「当真」这回事
你用了好几年 MySQL,它是个好脾气的伙伴——你丢什么它大都收下,实在不行就悄悄改一改,几乎从不为难你。这一章要说的是:这份好脾气,是有代价的。而 PostgreSQL 换了一种活法,它把你告诉它的每一件事,都当真。
一个让你后背发凉的例子
假设你在做一个订单系统,建了一张最普通不过的表:
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 NULL、UNIQUE、CHECK (balance >= 0)、外键,数据库就把它们当成铁律去执行——哪怕你的应用有 bug、哪怕有人绕过应用直接连库改数据。 - 事务当真(卷 IV):一笔转账要么全成、要么全不成,绝不会「扣了这边、没加那边」;连
CREATE TABLE这样的 DDL 都能包在事务里回滚——这一点 MySQL 至今做不到。 - 你的查询也当真(卷 V):你写下一句
SELECT,只说了「我要什么」,没说「怎么拿」。PostgreSQL 会认认真真估算十几条可能的执行路径、比较它们的成本,挑出最省的那条。它把你的声明式请求当真对待,而不是傻乎乎照字面顺序执行。
这四根支柱,就是这本书的卷 III 到卷 V。而在这之前的卷 II,我们会先把你已经会的那些 SQL(SELECT、JOIN、GROUP BY)往深里挖一层——因为「当真」也体现在语义上:PostgreSQL 严格遵守关系模型和三值逻辑,不像某些数据库为了「方便」而在语义上打折扣。
MySQL 为什么会「不当真」
这里要说句公道话:MySQL 的宽松不是因为它「差」,而是历史与取舍的产物。理解这段历史,能帮你更客观地看待两者。
MySQL 诞生于上世纪 90 年代,它最初的定位是「快,够用就行」——给 Web 应用当一个又快又简单的数据存储。它早年的默认存储引擎 MyISAM 压根没有事务,也不支持外键。为了「方便」和「不报错打断用户」,它选择了宽松:非法值就修正一下存进去,给个 warning 了事。这套「宁可存错、不要报错」的哲学,在那个「网站挂了比数据错了更要命」的年代,是有道理的。
后来 MySQL 也在慢慢「变严」。它引入了 InnoDB 引擎(有事务、有外键),并在 5.7 版本把严格模式(STRICT_TRANS_TABLES)设为了默认。所以今天一个配置得当的现代 MySQL,很多上面的坑已经会报错了。但问题在于:
- 宽松是它的历史默认,无数存量系统、老教程、老习惯仍带着这个包袱;一个
sql_mode配错,坑就回来了。 - 它的严格是「后来补上的一档开关」;而 PostgreSQL 的严格是与生俱来、无法关闭的底色——它从一开始就是伯克利的一个学术数据库项目,血统里就写着「正确性优先」。
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 也「当真」一点,去检查它的 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,你都能亲手敲一遍。