有时候不想写代码,只想看一眼 MySQL 那个绿色的 LOG 日志,就忍不住想在脑子里构造一个个小世界。

比如我想让“张三”和“李四”变成一对,结局张三突然改了密码,李四就跟着变了。

这感觉就像两个人共用一个银行账户,密码改了,钱就没了,要不就他们能有某种机制说“互不干涉”。SQL 实际上就是给这种关系找到的路。我们不用管它到底如何运作的,只要知道能达成目标就行。 在 Django 里,要做这种关系最直接的方式就是 `ForeignKey`。

这玩意儿就像是给两个人的名字加上了一个“务必绑定”的锁。在模型里定义 `Student` 和 `Teacher` 这种一对多,要么 `Product`、`Order` 这种一对多的时候,用 `ForeignKey` 是最标准的写法。它告诉数据库:A 表里的每一行,都得在 B 表里查找到对应的记录,不能少,也不能多。

要是找不到,数据库会直接报错,这种报错的直观性挺强,能让开发者一眼看出逻辑是不是对的。 有时候,两个表之间是一锤子买卖,不需求哪位绑定哪位,比如“员工”和“部门”。

这时候 `OneToOne` 感觉就有点像“一对一”的公约数。

只要你确认了这两组数据务必是一对一,就用这个。

不过要注意,在 Django 里,`OneToOne` 和一般/平平的一对多实际上是一模一样的,出于 `ForeignKey` 默认就是一个 OneToOne 关系。多出来的 `OneToOne` 标签只是在数据库建表语句里,告诉 DBA 要搞个主键分组,要么搞个外键约束,叫 `ON DELETE CASCADE` 之类的,本质上还是那套逻辑。 再想想现实世界里的关系,比如“商品”和“仓库”。商品在 `Goods` 表里,仓库在 `Warehouse` 表里。一个仓库里可能有几百个商品,这个关系忒多了。

这时候就要用到 `ManyToMany`。它的逻辑是双向的,既能在 `Goods` 表里查哪个仓库有货,也能在 `Warehouse` 表里查哪个商品在哪。

这个关系不需求预先定义哪位哪位哪位,数据库会自动处理,就像社交网络里的人脉关系一样,你点一下,那边也显示你。 不过 `ManyToMany` 有个坑,就是性能。出于要查两个表,数据得对齐,处理起来比 `OneToOne` 慢,特别是在大数据量要么高频查找的时候。

这时候就得有点技巧了。

比如有个“评论”表,关联“用户”和“文章”。

要是评论用户和文章是固定的,那能够用 `OneToOne` 要么 `ForeignKey`。但要是加上“置顶”要么“标记”这种状态,那就得用 `ManyToMany`。

这时候,做个自定义模型 `Comment`,字段里有 `user_id` 和 `article_id`,但这两个不是 `ForeignKey`,而是 `Field`。

这样在写查询的时候,就能够加上 `alias='user'` 和 `alias='article'` 来给它们起别名。

这样能省掉数据库里浪费的空间,查起来也更快。 写模型的时候,实际上还有个小技巧能够用。

比如我想给一个字段起个备注,但又怕数据库里存了忒多字符串数据

这时候能够用 `CharField` 加点 `max_length`。

比如 `description` 字段,设置成 `CharField(max_length=1000)`。

要是默认值写死了,比如 `"暂无"`,那实际上就浪费了存空间。能够用 `blank=True` 加上 `default=...`,要么干脆 `null=True`,让数据空着就行。空的数据数据库里就是 0 字节,存数据的时候内存占用就小多了。 SQL 语句有时候写出来看着挺长,但实际运行起来可能挺好办。

比如 `SELECT FROM User WHERE id = 100;`。大量时候我们不用连起来写,直接写 `id` 就行。出于 `id` 一般是自增的,要么唯一索引,查出来一行数据就充足了,不用全表扫描。有些建表语句里,为了省空间,可能会写 `INDEX(id)` 要么 `CHAR(10)` 之类的。别管它是如何建的,只要 `SELECT` 出来的结局是对的就行。 有时候数据量特别大,比如一个用户表有上万个 ID。

这时候要是直接去 `SELECT `,内存可能开不出来了,要么忒慢了。

这时候能够写个 `LIMIT` 要么 `WHERE id IN (...)`。

比如查最近 100 个评论,就写 `SELECT FROM Comment WHERE created_at > '2023-01-01' LIMIT 100;`。

这样既管住了数据量,又避免了全表扫描的噩梦。 还有种情况,两个表关联的时候,中间表的数据量特别大,害得查询拖沓。

这时候得寻思缓存。

比如一个“商品 - 仓库”的关联表,专门存 ID 到 ID 映射。查询的时候,先查这个缓存表,然后再去 `Goods` 和 `Warehouse` 表里找对应的 ID。

这样就能省掉一局部 JOIN 的过程,速度快多了。 最终,别忘了定期检查数据

比如 `COUNT()` 算一下总数,`DISTINCT` 算一下不同用户的数量。

有时候数据会积压,要么逻辑会有漏洞,用 SQL 查出来看看,比猜要好得多。SQL 别看只是查询语句,但它代表了整个数据库的“语言”,只要掌握根本语法,大局部业务逻辑都能搞定。