如何用 Django 搞定 SQL 的 “脏活累活”

68 阅读7分钟

如果要用更少的代码构建更高效的应用程序,并与高度可扩展的数据库建立连接,Python,尤其是 Django 往往是必不可少的。本文将简要谈谈在使用 SQL 和 Python 构建和支持现代应用的过程中,该如何减小日常工作中的摩擦,并将应用中蕴含的复杂性抽象出来,从而简化开发者的日常工作。

过于深入的技术细节先抛开不谈,不妨就让我们假设:

  • SQL 是专为 SQL 数据库优化的
  • Python 并没有为 SQL 数据库进行优化

下文将介绍如何在无需实际编写 SQL 语句的情况下,通过 Python 和相关工具来执行原始 SQL 命令。为此我们将使用 Django 的数据建模能力,但具体语法其实与 Python 中的 SQLAlchemy 包非常类似。


延伸阅读,了解 Akamai cloud-computing

[出海云服务,选择 Akamai Linode!](www.akamai.com/zh/solution…&utm_id=APJCC2023)


这就开始吧!

这是一个 Django 数据模型范例:

class BlogArticle(models.Model):
    user = models.ForeignKey(User, default=1, on_delete=models.SET_DEFAULT)
    title = models.CharField(max_length=120)
    slug = models.SlugField(blank=True, null=True)
    content = models.TextField(blank=True, null=True)
    publish_timestamp = models.DateTimeField(
        auto_now_add=False,
        auto_now=False,
        blank=True,
        null=True,
    )

假设该模型位于一个名为 Articles 的 Django 应用中(Django 应用本质上是指构成整个 Django 项目的不同组件)。

那么我们就有两个名称需要处理:

  • Articles(应用名)
  • BlogArticle(模型名)

结合在一起,就可以形成如下的 SQL 表名称:

articles_blog_article

接下来就可以看着 Django 为我们变魔术了。

如果使用 MySQL shell,将能看到这样的结果:

mysql> SHOW TABLES;
+------------------------------+
| Tables_in_cfe_django_blog_db |
+------------------------------+
| articles_blog_article        |
| auth_group                   |
| auth_group_permissions       |
| auth_permission              |
| auth_user                    |
| auth_user_groups             |
| auth_user_user_permissions   |
| django_admin_log             |
| django_content_type          |
| django_migrations            |
| django_session               |
+------------------------------+
11 rows in set (0.01 sec)

接下来一起看看 Django 模型中的列:

mysql> DESCRIBE articles_blog_article;
+------------------------+--------------+------+-----+---------+----------------+
| Field                  | Type         | Null | Key | Default | Extra          |
+------------------------+--------------+------+-----+---------+----------------+
| id                     | bigint       | NO   | PRI | NULL    | auto_increment |
| title                  | varchar(120) | NO   |     | NULL    |                |
| slug                   | varchar(50)  | YES  | MUL | NULL    |                |
| content                | longtext     | YES  |     | NULL    |                |
| publish_timestamp      | datetime(6)  | YES  |     | NULL    |                |
| user_id                | int          | NO   | MUL | NULL    |                |
+------------------------+--------------+------+-----+---------+----------------+

12 rows in set (0.00 sec)

除了编写一个最基本的配置外,Django 在这个 MySQL 数据库中帮助我们完成了所有与 SQL 有关的工作。其实到这里,Django 并没有表现出什么让人印象深刻的作用。实际上,本文中涉及到的大部分内容并不是为了凸显可将 Django 或 Python 作为 SQL 的替代品,而是为了向大家展示它在 “抽象” 方面的作用。

Python 的崛起和 “摩擦” 的代价

一起来看看阻力最小的路径是什么样的,以及为什么说 Python 是利用 SQL 的最佳方式。

Python 代码的撰写、阅读、运行以及发布都很容易。但如果将 “Python” 换成其他任何一种编程语言,这个结论几乎就可以肯定无法成立了。JavaScript 虽然也是 Python 的 “竞争者”,但不少人至今依然会把它和 Java 混淆。这些观点都是概括性的,并非总是成立,但却是很多开发者经常会遇到的难题。

但实际上这种概括性的观点大部分时候都能成立,因为 Python 的语法实际上和英语语法几乎没有差别!

不信就一起比一比 SQL 语句以及 Python 和 Django 的语句吧:

  • SQL:SELECT * from articles_blog_article;
  • Python 和 Django:items = BlogArticle.objects.all ()

这两条语句可以产生相同的数据,不过 Python 语句会返回一个 Python 对象(项)的列表,几乎任何有经验的 Python 开发者都可以直接使用该列表。而 SQL 语句产生的结果必须首先进行转换,随后才能供 Python 应用使用。

字段描述

再来仔细看看 SQL 的字段描述:

+------------------------+--------------+------+-----+---------+----------------+
| Field                  | Type         | Null | Key | Default | Extra          |
+------------------------+--------------+------+-----+---------+----------------+
| title                  | varchar(120) | NO   |     | NULL    |                |
+------------------------+--------------+------+-----+---------+----------------+

将上述内容与 Django 的字段描述对比一下:

title = models.CharField(max_length=120)
  • 哪个结果更直观?
  • 哪个结果更容易让人理解发生了什么?
  • 哪个结果能够提供 “刚好够用” 的信息?

如果一个并非开发者的人看到了 varchar (120),会怎样理解这个内容?相信大家至少可以猜到 max_length=120 是什么意思。最酷的地方来了:完全正确!就是 “将字段限制在 120 个字符以内” 的意思。

向数据库添加数据

如果使用 Django:

BlogArticle.objects.create(
    title="Hello World",
    content="Coming Soon",
    slug="hello-world",
    publish_timestamp=None,
)

如果使用 SQL:

INSERT INTO `articles_blog_article` (`user_id`, `title`, `slug`, `content`, `publish_timestamp`) 
VALUES (1, 'Hello World', 'hello-world', 'Coming Soon', NULL);

说到简洁明了,这方面的冠军无疑就是 Django 和 Python 了。title = "Hello World" 无疑要比弄清楚 SQL 中的等效列(字段)值是怎么回事更容易。没错,只有当你明确知道自己在所什么时,SQL 中的这种写法才会足够高效。

添加多个行

如果使用 Django:

items = [
    BlogArticle(title='Hello Again 0', slug='hello-again-0', content="Coming Soon"),
    BlogArticle(title='Hello Again 1', slug='hello-again-1', content="Coming Soon"),
    BlogArticle(title='Hello Again 2', slug='hello-again-2', content="Coming Soon"),
    BlogArticle(title='Hello Again 3', slug='hello-again-3', content="Coming Soon"),
    BlogArticle(title='Hello Again 4', slug='hello-again-4', content="Coming Soon"),
]
BlogArticle.objects.bulk_create(items)

如果使用 SQL:

INSERT INTO `articles_blog_article` (`user_id`, `title`, `slug`, `content`, `publish_timestamp`) 
VALUES (1, 'Hello Again 0', 'hello-again-0', 'Coming Soon', NULL),
    (1, 'Hello Again 1', 'hello-again-1', 'Coming Soon', NULL),
    (1, 'Hello Again 2', 'hello-again-2', 'Coming Soon', NULL),
    (1, 'Hello Again 3', 'hello-again-3', 'Coming Soon', NULL),
    (1, 'Hello Again 4', 'hello-again-4', 'Coming Soon', NULL);

这再次证明了 Python 代码有着更高可读性,而 SQL 代码能针对实际数据提供更深入的见解。不过上述 SQL 代码依然是通过 Python 使用 Django 写出来的。很棒对吧!

请注意:上述对比并不是为了确定哪种技术才是利用 SQL 数据库的最佳方式,而是为了凸显 Python 在帮助开发者减轻学习和直接编写 SQL 语句的负担方面所蕴含的潜力。

有很多 Python 包可以帮助开发者编写原始 SQL 语句,包括但不限于:

  • Django
  • Pandas
  • SQLAlchemy
  • Polars
  • Dask
  • Vaex
  • Python’s built-in CSV Module
  • Tortoise ORM
  • Pony ORM
  • SQLObject

Django 替你负重前行

Python 这样利用 SQL 数据库的秘诀在于对象关系映射包(通常也被称之为 ORM)。我们可以将 ORM 看作一种 “中间人”,它可以帮助我们在任何特定语言的原生语法中移动数据。

上文我们展示了如何将 SQL 转换为 Django,接下来不放再发散思考一下还能怎样进行扩展。

假设数据库中有大量数据,并且我们编写了这样一条命令:

my_post = BlogArticle.objects.first()

该语句可以查询数据库,提取数据,将数据载入 Python Class 实例,并将其分配给变量 my_post。

这样我们就可以执行类似下面这样的操作:

# using a django-managed python shell
# via python manage.py shell
>>> print(my_post.title)
Hello World

本例中我们使用 “dot” 符号来访问上文提到的 BlogPost 这个 Django 模型的 title 字段。该字段对应于 SQL 数据库表 articles_blog_article 中的一个列。

借助 ORM,我们将能做到:

>>> my_post.title = "some other title"

在这个 Python Shell 会话的例子中,my_post.title 将始终保持为 "some other title"。然而底层的 SQL 数据库依然能将这个数据识别为 Hello World。数据库将保持原始数据,直到 Python 最终将数据的变化提交(即.save ())到数据库。如果 Python 从不提交该数据,那么数据库的内容将永不更新。这也恰恰是 ORM 的神奇之处。我们可以在不影响实际已存储数据的前提下使用和更改数据。当需要对数据库中的内容进行实际修改时,可以运行:

>>> my_post.title = "some other title again"
>>> my_post.save()

在针对数据库运行.save () 后,数据库中特定的行将被更新为新的标题,进而与上述 Python 代码中给出的字符串保持一致。别忘了,.save () 方法是专门用于向 Django 模型提交数据库改动的,.save () 对 Python 的 Class 没有任何实际意义,除非首先继承了形式的 Django 模型类。


这篇文章的内容感觉还行吧?有没有想要立即在 Linode 平台上亲自尝试一下?别忘了,现在注册可以免费获得价值 100 美元的使用额度,快点自己动手体验本文介绍的功能和服务吧↓↓↓

出海云服务,Akamai 是您的不二之选!

欢迎关注 Akamai ,第一时间了解高可用的 MySQL/MariaDB 参考架构,以及丰富的应用程序示例。