卷 I · 起底CH 01深度 01/24

「当真」这回事

你用了好几年 MySQL,它是个好脾气的伙伴——你丢什么它大都收下,实在不行就悄悄改一改,几乎从不为难你。这一章要说的是:这份好脾气,是有代价的。而 PostgreSQL 换了一种活法,它把你告诉它的每一件事,都当真

当真严格 vs 宽松数据可信全书主线

一个让你后背发凉的例子

假设你在做一个订单系统,建了一张最普通不过的表:

CREATE TABLE orders (
  id      integer,
  ship_by date,        -- 承诺的发货日期
  code    varchar(4),  -- 四位短码
  qty     integer
);

某天,上游传来一批脏数据。也许是前端没校验,也许是别的系统对接出了岔子,总之你的 INSERT 里混进了几个不干净的值。我们来看 MySQL(传统的、宽松模式下的 MySQL)会怎么处理它们:

-- 一个根本不存在的日期
INSERT INTO orders (id, ship_by) VALUES (1, '2026-02-30');
-- 一个超过 4 个字符的短码
INSERT INTO orders (id, code)    VALUES (2, 'HELLO');
-- 一个不是数字的数量
INSERT INTO orders (id, qty)     VALUES (3, 'abc');
Query OK, 1 row affected, 1 warning
Query OK, 1 row affected, 1 warning
Query OK, 1 row affected, 1 warning

-- 一年后,你去查这张表,看到的是:
 id | ship_by    | code | qty
----+------------+------+-----
  1 | 0000-00-00 |      |
  2 |            | HELL |
  3 |            |      |   0

看清楚发生了什么:三条写入全都「成功」了。那个不存在的日期变成了 0000-00-00——一个连它自己都不承认是合法日期的怪东西;'HELLO' 被悄悄切成了 'HELL',尾巴丢了;'abc' 变成了 0。每一条都给了个 warning,可谁会去逐条看 warning 呢?这些值就这么进了你的库,安静地躺在那里,等着某一天在报表里、在对账时、在一个愤怒的客户面前爆炸。

而你,将永远无法从数据里追查出「这个 0000-00-00 当初到底是什么」。信息在写入的那一刻就已经丢了。

现在看 PostgreSQL 面对同样三条写入:

db=# INSERT INTO orders (id, ship_by) VALUES (1, '2026-02-30');
ERROR:  date/time field value out of range: "2026-02-30"
LINE 1: INSERT INTO orders (id, ship_by) VALUES (1, '2026-02-30');
                                                    ^

db=# INSERT INTO orders (id, code) VALUES (2, 'HELLO');
ERROR:  value too long for type character varying(4)

db=# INSERT INTO orders (id, qty) VALUES (3, 'abc');
ERROR:  invalid input syntax for type integer: "abc"

三条,全部拒绝。没有一条脏数据进得来。PostgreSQL 的态度很简单:你说 ship_by 是个 date,那我就只接受真正的日期;2026-02-30 不是日期,我不会替你猜你想要什么,也不会随便找个值塞进去——我告诉你这里错了,然后什么都不做。

▶ 动手 · 当真对照台

下面这台对照台把这件事摊开给你:左边是 MySQL 的传统脾气,右边是 PostgreSQL。挑一条写入,看两边各自怎么处理同一个值。注意最后一条——合法的东西,两边都收;PostgreSQL 的严格不是刁难,是只在该拒绝时拒绝

「当真」是一种一以贯之的态度

上面这个例子只是「类型」这一个方面。但 PostgreSQL 的「当真」不是一处两处的较真,而是一种贯穿始终的世界观。你在这本书里会一次次撞见它,在四个方向上:

  • 类型当真(卷 III):一列声明成 integer,那它每一格就都是整数,不多不少;numeric 存的钱一分不差,timestamptz 存的时间带着时区、不会算错。
  • 约束当真(卷 III):你写下 NOT NULLUNIQUECHECK (balance >= 0)、外键,数据库就把它们当成铁律去执行——哪怕你的应用有 bug、哪怕有人绕过应用直接连库改数据。
  • 事务当真(卷 IV):一笔转账要么全成、要么全不成,绝不会「扣了这边、没加那边」;连 CREATE TABLE 这样的 DDL 都能包在事务里回滚——这一点 MySQL 至今做不到。
  • 你的查询也当真(卷 V):你写下一句 SELECT,只说了「我要什么」,没说「怎么拿」。PostgreSQL 会认认真真估算十几条可能的执行路径、比较它们的成本,挑出最省的那条。它把你的声明式请求当真对待,而不是傻乎乎照字面顺序执行。

这四根支柱,就是这本书的卷 III 到卷 V。而在这之前的卷 II,我们会先把你已经会的那些 SQL(SELECTJOINGROUP BY)往深里挖一层——因为「当真」也体现在语义上:PostgreSQL 严格遵守关系模型和三值逻辑,不像某些数据库为了「方便」而在语义上打折扣。

MySQL 为什么会「不当真」

这里要说句公道话:MySQL 的宽松不是因为它「差」,而是历史与取舍的产物。理解这段历史,能帮你更客观地看待两者。

MySQL 诞生于上世纪 90 年代,它最初的定位是「快,够用就行」——给 Web 应用当一个又快又简单的数据存储。它早年的默认存储引擎 MyISAM 压根没有事务,也不支持外键。为了「方便」和「不报错打断用户」,它选择了宽松:非法值就修正一下存进去,给个 warning 了事。这套「宁可存错、不要报错」的哲学,在那个「网站挂了比数据错了更要命」的年代,是有道理的。

后来 MySQL 也在慢慢「变严」。它引入了 InnoDB 引擎(有事务、有外键),并在 5.7 版本把严格模式(STRICT_TRANS_TABLES)设为了默认。所以今天一个配置得当的现代 MySQL,很多上面的坑已经会报错了。但问题在于:

  • 宽松是它的历史默认,无数存量系统、老教程、老习惯仍带着这个包袱;一个 sql_mode 配错,坑就回来了。
  • 它的严格是「后来补上的一档开关」;而 PostgreSQL 的严格是与生俱来、无法关闭的底色——它从一开始就是伯克利的一个学术数据库项目,血统里就写着「正确性优先」。
✎ 掌故 · Post-Ingres

PostgreSQL 的名字来自 Post-Ingres——它是 Ingres(70 年代最早的关系数据库之一)的后继者,由伯克利的 Michael Stonebraker 团队在 80 年代发起。它一开始就带着强烈的学术基因:把关系模型、类型系统、扩展性做对,而不是「先跑起来再说」。那头蓝色的大象 Slonik 背后,是三十多年「当真」的积累。

当真的代价,和它的回报

「当真」不是免费的。你会付出一些代价,也会得到确定的回报,值不值得,你读完这本书自会判断——但先把账摊开:

方面宽松(历史 MySQL)当真(PostgreSQL)
脏数据写入大多能写进去当场报错拒绝
bug 暴露的时机很晚——在数据里,事后才发现很早——在写入时,立刻发现
前期投入少,随便建表就能用多一点,要认真声明类型和约束
数据的可信度存疑,你不敢完全相信库里的值高,库里的每个值都符合它声明的规矩
报错体验安静,不打断直白,会打断你

核心的取舍其实是一句话:你想让 bug 早暴露,还是晚暴露?「当真」把痛苦提前——它在你写入脏数据的那一刻就拦住你,让你当场修。宽松把痛苦推后——它让你此刻很顺,却把一颗定时炸弹埋进了数据里。软件工程这些年反复教我们的一课是:越早发现的错,越便宜。一个在 INSERT 时被拒绝的错,你花五分钟改;一个躺进数据库、半年后在财务报表里被发现的错,可能要花五天去追查、去补救、去向客户道歉。

◆ 全书主线

很多人把数据库当成一个「桶」:把行丢进去,需要时捞出来。MySQL 的历史默认纵容了这种想法。

PostgreSQL 把数据库当成一台对正确性较真的「引擎」。它认真对待你声明的每一件事——类型、约束、事务,乃至你那句查询。这本书的每一章,都是这个态度在某个具体问题上的展开。读完你会发现:所谓「PostgreSQL 更强大」,强的不是功能列表更长,而是它值得信任——你敢相信库里的每一个值,都名副其实。

这本书怎么带你体会「当真」

接下来的路线是这样的。卷 I(你正在读)先把地基打好:装好 PostgreSQL、理解「关系就是集合」、建起一个贯穿全书的示例库。卷 II 把你会的 SQL 挖深。卷 III、IV、V 分别是「当真」的三根支柱:类型与约束、事务与 MVCC、查询规划器。卷 VI 落到生产,并给你一张从 MySQL 搬家的对照表。

而且,为了让你不只是「听我说」,书里最关键的两个机制——查询规划器MVCC——是用真代码写出来、就在网页里跑的。你能亲手拨弄它们,看 PostgreSQL 是怎么「认真思考」的。上面那台当真对照台,就是第一个。

⇄ 对照 MySQL

如果你想让手头的 MySQL 也「当真」一点,去检查它的 sql_mode。现代 MySQL(5.7+)默认包含 STRICT_TRANS_TABLES,会把上面很多坑变成报错。但要注意:

  • STRICT 模式是可以被关掉或改写的(一句 SET sql_mode='' 就回到宽松),而 PostgreSQL 的严格没有开关
  • 即便开了严格模式,MySQL 在一些细节上仍比 PostgreSQL 宽松,比如 GROUP BY 的处理(卷 II 会讲)、隐式类型转换的规则。
  • MySQL 长期没有 CHECK 约束的真正执行(8.0.16 之前直接忽略你写的 CHECK),这一点我们在卷 III 细说。

换句话说:你可以把 MySQL 得更当真,但 PostgreSQL 生来就当真。这是气质的差别。

这一章的一句话

MySQL 的历史默认是「宁可悄悄存错,不要报错打断」;PostgreSQL 的底色是「不合规就当场拒绝、绝不替你蒙混」。这份「当真」,就是这本书要带你逐层认识的东西。

下一章,我们花三分钟把 PostgreSQL 真的跑起来,认识它的门面 psql——这样后面每段 SQL,你都能亲手敲一遍。