都 2025 年了,还不试试 PostgreSQL?
一、前言:MySQL 已经成了惰性的象征,而非技术的选择
MySQL 曾经是互联网时代的功臣,它简单、便宜、够快。但到了 2025 年,它更多代表的是行业的惯性与保守。人们继续用它,不是因为它先进,而是因为它“还活着”。然而,如果你把目光放到数据库设计层面,就会发现:MySQL 根本不是一个完整的关系数据库,而是一个带 SQL 外壳的存储系统。
它的事务,只覆盖了一部分数据; 它的 ACID,是有注脚的; 它的优化器,是随缘的; 它的触发器,是残废的; 它的约束,是可关闭的。
MySQL 靠“够用”生存; PostgreSQL 靠“正确”延续。
两者之间的区别,不是实现多少特性, 而是——是否尊重数据库科学本身。
二、优化器:MySQL 靠猜,PostgreSQL 靠算
MySQL 的查询优化器还停留在上个世纪。它基于规则,不基于代价。
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
status VARCHAR(20),
created_at DATETIME,
INDEX idx_user_status (user_id, status)
);
EXPLAIN SELECT * FROM orders WHERE user_id = 42 AND status = 'done';
EXPLAIN SELECT * FROM orders WHERE status = 'done' AND user_id = 42;逻辑上完全相同的两条 SQL:
- 第一条走索引;
- 第二条却全表扫描。
因为 MySQL 优化器死板地按索引定义顺序匹配列,不会重排谓词,也不计算组合代价。稍不注意写法,性能直接爆炸。
而 PostgreSQL 的优化器是真正的代价模型(Cost-Based Optimizer)。它通过列统计与数据分布做决策,执行计划稳定、可预测。
PostgreSQL 的计划是理性计算; MySQL 的计划是随机猜测。
当你的系统需要确定性性能,MySQL 这个优化器就成了炸弹。
三、ACID 的幻觉:回滚之后,MySQL “干净地”抹掉历史
MySQL 的最大谎言,是那句印在官网上的口号:
“InnoDB fully supports ACID transactions.”
这句话听上去像承诺,实际上是选择性履行。遇见 DDL,MySQL 立刻撕毁契约。
看看这一段再平常不过的表迁移事务:
START TRANSACTION;
-- 1. 改名原表
RENAME TABLE metrics TO metrics_1;
-- 2. 按旧结构创建新表
CREATE TABLE metrics LIKE metrics_1;
-- 3. 导入旧数据
INSERT INTO metrics SELECT * FROM metrics_1;
-- 4. 删除旧表
DROP TABLE metrics_1;
-- 5. 写入日志(假设这里出错)
INSERT INTO system_events(event_type, description)
VALUES ('table_upgrade', 'metrics table upgraded');
ROLLBACK;你以为 ROLLBACK 是“回到从前”?
在 MySQL 里,它意味着“请擦干净尸体再走”。
执行之后你会得到:
- 原表
metrics被改名再删除; - 新表
metrics还在,但空无一物; - 数据彻底消失;
- 那条日志既没写入,也没留下痕迹。
你手动发起了回滚,MySQL 则帮你干净地抹掉了历史。
更荒谬的是,这不是 bug,而是官方设计。 任何 DDL(RENAME、CREATE、DROP)都会触发隐式提交。事务还没结束,它就自作主张地写死文件。
结果是:回滚只是摆设,原子性形同虚设。你的事务,不是安全网,而是引爆器。出错时,它炸的不是异常,而是数据本身。
PostgreSQL 的对比:真正的事务,是一次数据库级时间穿越
在 PostgreSQL 中,同样的操作是完全事务化的:
BEGIN;
ALTER TABLE metrics RENAME TO metrics_1;
CREATE TABLE metrics (LIKE metrics_1 INCLUDING ALL);
INSERT INTO metrics SELECT * FROM metrics_1;
DROP TABLE metrics_1;
INSERT INTO system_events(event_type, description)
VALUES ('table_upgrade', 'metrics table upgraded');
ROLLBACK;一旦执行 ROLLBACK:
- 所有表、结构、数据、索引全部回退;
- WAL 写前日志完整重放;
- 系统状态恢复如初,连指针都不漏一页。
这不仅仅是“支持 DDL 事务”,这是数据库哲学分界线的实证:
PostgreSQL 的事务作用于整个数据库状态; MySQL 的事务只作用于部分数据文件。
前者是有意识的系统,它理解什么叫“一致性”、“原子性”、“时间线”;后者只是一个文件操作堆栈——连“状态”都不懂。
毫不夸张地说:
PostgreSQL 的事务是一个原子行为; MySQL 的事务是一个概率事件。
前者以逻辑守护数据; 后者赌机器别出错。
PostgreSQL 管整个宇宙;MySQL 只管几张文件。 这是工程实现的鸿沟,更是理念上的耻辱。
四、触发器与逻辑:MySQL 的断肢,PostgreSQL 的神经系统
MySQL 到今天的触发体系依旧严重残缺:
- 每个事件只能定义一个触发器;
- 不支持语句级触发;
- 不支持条件触发;
- 调试如地狱,复用无可能。
2025 年,你依然能看到这样的报错信息:
ERROR 1235 (HY000): This version of MySQL doesn't yet support multiple triggers for the same table.而 PostgreSQL 的触发体系,是成熟的事务内逻辑层:
- 行级与语句级触发并存;
- 支持条件判断(
WHEN); - 可指定顺序、共存多个;
- 可用多语言编写(PL/pgSQL、Python、C)。
在 PostgreSQL,触发器能形成业务层逻辑护城河; 在 MySQL,它只是挂件。
五、约束与一致性:MySQL 允许造假,PostgreSQL 不允许撒谎
MySQL 允许你在一行命令里掐死完整性保障:
SET FOREIGN_KEY_CHECKS = 0;然后导入任意垃圾数据。再恢复检查,它也不会重验。 完美的“装死”机制。
PostgreSQL 没有这种妥协选项。 约束永远生效、永远执行。 它不会假装一致 —— 它要么成功,要么拒绝。
MySQL 教你撒谎;PostgreSQL 逼你诚实。
更离谱的是,MySQL 在处理外键与复合索引时的行为,完全超出直觉。
假设你有一个主表与子表结构如下:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
name VARCHAR(50),
INDEX idx_user (user_id, name),
FOREIGN KEY (user_id) REFERENCES users(id)
);理论上,这里的外键只约束 user_id,与 name 无关。但在 MySQL 中,只因为你建立了联合索引 (user_id, name),它会在某些版本(尤其 5.x 与早期 8.0)中无端把第二列 name 一并纳入外键校验逻辑。任何 name 冲突、甚至非唯一匹配,都会触发外键报错。这意味着你根本没定义外键到 name,却被 MySQL 的外键机制“顺手管了”。
PostgreSQL 不会做这种事。 它认得标准:外键依赖主键或唯一约束列,不会跨字段、不会超范围、不会瞎猜。
MySQL 的问题在于:它将“联合索引”视作“外键实现细节”,把数据约束混在索引选型里,最终让行为变得既不确定又难以调试。
PostgreSQL 则严格区分:索引是优化器的工具,外键是一致性的契约。MySQL 把契约和工具绑死在一起。
当你加个索引时,你同时改变了逻辑规则——这不是数据库,这是雷区。
六、“数据库只是大号 Excel”?
不,那只是被 MySQL 教坏的一代人
有人说:
“逻辑不要放数据库,数据库只是存储。”
这句话听上去理性,其实是悲哀。
悲哀的是:MySQL 把开发者养成了“不信任数据库”的人。
因为:
- 事务不完整;
- 约束能关;
- 触发器假死;
- 优化器靠缘分。
于是程序员被迫把一致性逻辑搬到应用层,再对自己说:“逻辑不应在数据库中做。”
结果就是 ORM、幂等、防重复、补偿机制满天飞。只是因为 MySQL 从未完成应尽职责。
而 SQL 标准在 1992 年就已完整确立:
- 标准事务语义(包含 DDL);
- 外键与约束机制;
- 触发器与存储过程;
- 视图、模式、授权体系。
换句话说,在 MySQL 出生之前,SQL 世界已经有了成熟、完整、被各大数据库验证过的科学定义。
但到了 1995 年,MySQL 出场时,它做了一个惊人的选择: 它没有遵守科学,不继承标准,而是自创一套残废版 SQL,命名为——“My SQL”。注意那个 “My”:它不是对个人的归属表达,而是一个注脚
“我只承认属于我自己的 SQL 规则。”
于是它扭曲了语言逻辑、删掉了事务 DDL、阉割了触发器、弱化了外键,再把这些残缺包装成“轻量”“高性能”“互联网友好”。 事实上,那不过是权衡实现复杂度后的技术懒惰与规范背叛。
这就是 MySQL 的原罪:当全世界都在遵守《SQL-92》, 它却在实现 “Only My SQL”。
结果,MySQL 教坏了一代开发者:大家开始认为数据库就是 CRUD 容器;开始把一致性逻辑扔回应用代码;一边疲于修补,一边自诩解耦优雅。
MySQL 不止是技术问题,它是一种文化病毒 ——它让人们习惯无标准、无事务、无尊严的数据库生态, 并且还为此自豪。
当他们说“数据库只是 Excel”,真正的意思是:
“我们用的 MySQL 就像 Excel——能存点数据,却没灵魂。”
七、有人说“事务里做 DDL 不科学”?
不,那只是被 MySQL 吓坏的工程师自我安慰
会有人批评文中这一点,说:
“在事务里执行 DDL 是不科学的设计。”
这句话听起来高冷,实际上是 MySQL 弱点导致的错觉。他们不是在阐述数据库原理,而是在为 MySQL 找借口。
事实是:事务中执行 DDL 并不反常,这本应是关系数据库的标准语义。 但因为 MySQL 拉跨,他们被迫认为‘不能做’,然后自我合理化。
他们记住的是 MySQL 的崩溃方式, 于是开始以为数据库就该如此; 于是把“不敢”包装成“原则”; 把数据库降级成“存储层”; 再用代码去弥补数据库应有的功能。
PostgreSQL 没这个问题。 你可以在事务里安全地改表、建表、删表、回滚。 因为它的 DDL 就是事务的一部分, 它不装模作样,不偷偷提交。
所以请停止把 MySQL 当作数据库的定义。 它只是数据库的简化版、阉割版、历史遗迹。
真正科学的,是 PostgreSQL; 真正错误的,是被 MySQL 教坏的世界。
要正确性,就别怕高级。 要数据库,就别怕事务中的 DDL。
MySQL 的恐惧,不该成为你的惯性。 真正的数据库,不需要妥协。
MySQL 教人屈服;PostgreSQL 教人尊重。
八、分库分表:互联网最成功的错误设计
所谓“分库分表”,在今天被吹嘘成互联网架构的成功案例。但事实恰恰相反——它不是进化结果,而是 MySQL 贫瘠设计的自我赎罪。
MySQL 从来没有真正的分区机制、全局索引、全局统计信息,也没有跨表事务优化器。当数据量增大,它无法逻辑扩展,只能物理切割。 于是“分库分表”登场——不是因为聪明,而是因为没办法。
从此,应用层开始接管数据库的职责。程序员被迫在业务代码中:
- 自行计算分片位置;
- 写路由中间件决定 SQL 去哪;
- 实现伪分布式事务补偿;
- 拼接跨库聚合查询;
- 再写一层定时脚本同步数据。
这一切,只是为了修补一个不支持现代分区能力的数据库。而这些补丁行为,居然被包装成“高并发经验”,仿佛“用应用弥补数据库的残缺”就成了架构智慧。这就像一辆没有刹车的汽车,司机们开始总结撞墙减速的技巧, 然后把它写进《高速驾驶指南》。
分库分表从来不是架构突破,而是数据库退化的症状。
PostgreSQL 从未需要靠这种土法生存。它的分区、并行与事务一致性都在数据库层自然发生。它遵循 SQL 标准,并实现了真正意义上的逻辑分区表——在保持事务一致的同时,可无限水平扩展。
例如,一个标准的时间分区表:
-- 创建主表并定义分区键
CREATE TABLE logs (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
action TEXT,
created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);
-- 创建分区表
CREATE TABLE logs_2024 PARTITION OF logs
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
CREATE TABLE logs_2025 PARTITION OF logs
FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
-- 插入数据时自动路由到正确分区
INSERT INTO logs (user_id, action, created_at)
VALUES (1, 'login', now());
-- 查询仍然是针对一个逻辑整体
SELECT user_id, COUNT(*) FROM logs
WHERE created_at >= '2025-01-01'
GROUP BY user_id;
-- 扩展新分区只需一句
CREATE TABLE logs_2026 PARTITION OF logs
FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');数据量再大,它依然是一个逻辑整体。分区是扩展,不是切割;事务仍完整,优化依然智能;一致性从未退让。
因为 PostgreSQL 的设计宗旨是:
当系统变大,正确性不该变小。
而 MySQL 的哲学是另一种:
“先跑起来,崩溃再想办法。”
于是,一个数据库不承担数据库的职责,行业就开始弥补它的缺陷——写代理、造中间件、讲分片策略、造轮子、开大会、出白皮书。所有人都在修补 MySQL 的伤口,而非构建数据库的未来。
如果说 PostgreSQL 推动了数据库技术的演化,那么 MySQL 推动的,只是分库分表的产业链。它让无数人困在自造的迷宫里,同时以此误以为“这就是大规模架构的真相”。
真相是:成熟的数据库不需要你去分它。分库分表不是荣誉勋章,而是系统设计的病历单。它见证的不是成功,而是退化——见证了一个数据库如何拒绝成长,又如何让整个行业去替它长大。
PostgreSQL 让系统扩展正确性; MySQL 让系统扩展复杂性。
分库分表不是智慧,是悲剧的形式美。
九、结语:要正确性,不要幻觉
MySQL 的哲学是:差不多就行。
PostgreSQL 的哲学是:必须正确,否则失败。
| 对比项 | MySQL 5.7 | PostgreSQL 12 |
|---|---|---|
| 事务原子性 | 仅 DML,DDL 会隐式提交 | 全局原子性 |
| 优化器 | 规则匹配,性能漂移 | 成本模型,推理稳定 |
| 外键与约束 | 可关闭或跳过 | 严格强制执行 |
| 触发器 | 功能残缺 | 事务级完整 |
| DDL 事务 | 不支持,必隐式提交 | 完全支持,可回滚 |
| SQL 标准 | 方言化实现 | 完全兼容 ANSI SQL |
| 扩展方式 | 分库分表(应用层路由) | 原生分区表(数据库层) |
| 事务一致性 | 跨库 XA,慢如狗 | 全局 ACID |
| 查询语法 | 动态拼接 | 统一 SQL |
| 扩容成本 | 停机 + 迁移 | ATTACH PARTITION |
| 是否优雅 | 像修下水道 | 像写 SQL |
到了 2025 年,
如果你还在忍受一个:
ROLLBACK会删光你表的数据库;- 允许你关闭外键检查的数据库;
- 靠猜测计划运行的数据库;
- 需要你写中间件来“分库分表”的数据库
那你不是在用数据库, 你是在赌数据库别出事故。
MySQL 属于过去 —— 它是“差不多”的象征; PostgreSQL 属于未来 —— 它是“正确性”的代名词。
停止为 MySQL 的残缺买单。
真正的架构师,不造轮子补洞,而是选择一个不用补的数据库。
要么 PostgreSQL,要么幻觉。
要么正确,要么坍塌。