被开除的DBA告诉你:为什么我赌上职业生涯也要选PostgreSQL
三年前,我因为在技术选型会上坚持用PostgreSQL而不是MySQL,被CTO当场叫去喝茶。结论是:你不懂业务,出去吧。
我当时年轻气盛,觉得真理站在我这边。现在回想起来,那场会议的PPT确实做得很烂,我的表达方式也有问题——上来就列benchmark,人家以为我在炫技。但核心观点没错:MySQL不是烂,只是被神话了。而PostgreSQL被严重低估了。
这篇文章不打算再写一篇“PostgreSQL vs MySQL功能对比”,那种表格满大街都是。我只想聊几件事:为什么我在真实生产环境里,一次又一次地倒向PostgreSQL。
一、事务隔离级别:你以为你懂,其实你被坑了
MySQL默认的隔离级别是可重复读(REPEATABLE READ),PostgreSQL默认是读已提交(READ COMMITTED)。很多MySQLer看到这里就炸了:什么?你用读已提交,不怕脏读?
先别急着喷。读已提交不会脏读,它保证的是:你的查询只能看到在你查询之前已经提交的数据。这是SQL标准定义的隔离级别,也是绝大多数业务场景真正需要的。
MySQL的REPEATABLE READ听起来更严格,但它有个骚操作——使用MVCC实现的同时,保留了间隙锁(Gap Lock)机制。这本意是防止幻读(Phantom Read),但代价是:并发写入时,锁的范围比你想象的大得多。
我经历过一次典型的MySQL锁死:
-- 场景:两个事务同时插入唯一键冲突的记录
-- 事务A
BEGIN;
INSERT INTO orders (user_id, product_id) VALUES (100, 200); -- 阻塞
-- 事务B
BEGIN;
INSERT INTO orders (user_id, product_id) VALUES (100, 201); -- 同样阻塞
-- 此时两个事务互相等待对方释放锁,死锁
-- MySQL检测到死锁,回滚其中一个
表面上看,这是唯一键冲突的问题。但深层原因是:MySQL在REPEATABLE READ下,间隙锁锁住了更大的范围。你以为你只是insert一条记录,实际上MySQL在考虑“万一有幻读呢”。
PostgreSQL怎么处理?它的MVCC+VISIBILITY CHECK机制让读和写互不阻塞。INSERT时,它只需要检查唯一性约束,不会去猜测“未来会不会有其他事务插入冲突数据”。读是快照读,写是当前读,各走各的路,谁也不耽误谁。
真实场景下,这种差异会直接影响你的系统吞吐量。我做过压测:同样硬件配置、同样并发数,PostgreSQL的TPS(每秒事务数)大约是MySQL的1.5到2倍,主要就是这个原因。
二、JSON支持:MySQL 5.7是残废,MySQL 8.0也只是能用
JSON成了互联网的事实标准。日志是JSON、配置是JSON、API请求体是JSON。你跟我说不用JSON字段?那是让人回到史前时代。
MySQL 5.7引入了JSON类型,8.0做了增强。但说实话,用过的都知道:体验相当割裂。
-- MySQL:查询JSON字段里的某个属性
SELECT JSON_EXTRACT(metadata, '$.user.name') FROM users;
-- 问题是,返回的是JSON字符串,还要再处理
-- 路径表达式用的是 $ 开头,跟标准JSON Path不太一样
PostgreSQL的JSONB类型是二进制存储,查询性能好很多:
-- PostgreSQL:查询JSONB字段
SELECT metadata->>'user'->>'name' FROM users;
-- ->> 返回文本,-> 返回JSON值
-- GIN索引直接加速JSONB字段的任意属性查询
更关键的是,PostgreSQL的JSONB支持创建表达式索引:
CREATE INDEX idx_users_email ON users ((metadata->>'email'));
-- 对JSON里的email字段建索引,查询直接走索引
-- MySQL的JSON索引?抱歉,你需要先提取再函数索引,效果差很多
我见过一个真实的反例:某团队用MySQL存用户行为数据,metadata字段里有几十种不同结构的数据。查询时全表扫描,每次都要JSON_EXTRACT折腾半天。迁移到PostgreSQL之后,同样的查询从3秒降到50毫秒——就靠JSONB的GIN索引。
当然,这里不是MySQL黑到底。MySQL 8.0的窗口函数、CTE(Common Table Expression)这些后来加的功能,追平了不少差距。但JSON处理这块,差距是设计理念上的差距,不是修修补补能赶上的。
三、复制机制:MySQL的主从是个历史包袱
MySQL的复制机制是binlog复制,逻辑复制。这个机制十几年前设计的,优点是简单,缺点是:数据一致性要靠人为干预。
MySQL主从延迟是个老大难问题。一个大事务在主库执行花了10秒,那么从库至少延迟10秒。这10秒里,从库的数据是过期的。你如果用读写分离,读到从库的数据可能是10秒前的数据。
解决方案?花钱买更贵的硬件、优化SQL逻辑、用半同步复制……都有用,但都是补丁。
PostgreSQL的逻辑复制更灵活。你可以选择复制整个数据库,也可以只复制某个表。10.0之后引入的物理复制,基于流式复制(WAL),延迟可以低到毫秒级。
更重要的是,PostgreSQL的多主复制(Multi-Master)方案更成熟。比如BDR(Bi-Directional Replication),支持多节点双向同步,适合跨地域部署。MySQL Group Replication是后来加的,稳定性在某些场景下还是差点意思。
这里说个真实踩过的坑:MySQL 5.7使用半同步复制,等待从库确认的机制在网络抖动时会降级为异步——也就是说,从库可能已经“返回了ACK”但实际没收到数据。这种隐式降级最可怕,它不会报错,只是在某个时刻让你的数据悄悄不一致。PostgreSQL的流复制机制更透明,你能看到真实的延迟状态。
四、扩展性:PostgreSQL是个操作系统,MySQL是个微波炉
这句话有点极端,但很形象。
PostgreSQL支持:
- 自定义类型(CREATE TYPE)
- 自定义索引(ACCESS METHOD)
- 自定义函数(支持C、Python、JavaScript、Perl等)
- 物化视图
- 规则系统(RULE)
- 触发器(行级、语句级、BEFORE、AFTER、INSTEAD OF)
- 存储过程(PL/pgSQL,和T-SQL完全不是一个量级)
有人说PostgreSQL是“一个开源的Oracle”,这话不算夸张。它的扩展性让它可以应对非常复杂的业务场景。
比如,我用PostgreSQL做过一件事:用自定义索引方法实现全文检索优化,结合分词器,效果不比Elasticsearch差多少。数据量在几千万级别时,完全不需要引入额外的搜索引擎。架构越简单,出问题的点就越少。
MySQL呢?它的插件系统很弱。MySQL 8.0加了个组件系统,但比起PostgreSQL的扩展性,还是差了一个量级。你想在MySQL里加个自定义索引类型?做梦吧。
这里可能有人要反驳:MySQL的InnoDB已经很好了,为什么要自定义?这话没错,但问题是:当标准功能不够用的时候,你是选择引入新组件(Redis、ES、MongoDB……),还是让数据库帮你解决? 引入越多组件,运维复杂度指数级上升。
五、说了这么多,MySQL就一无是处?
不是的。我没那么偏激。
MySQL的强项是:简单、成熟、生态好。WordPress用MySQL,Facebook用MySQL,GitHub用MySQL,这些不是偶然。MySQL的文档极其完善,社区资源丰富,运维人才遍地都是。招一个MySQL DBA比招一个PostgreSQL DBA容易多了,这在很多公司是现实考量。
另一个现实:阿里系的技术栈深度绑定MySQL。阿里贡献了大量MySQL优化patch和中间件(TDDL、Druid、Canal),这些在PostgreSQL生态里找不到对等物。如果你公司在阿里云上,MySQL的配套服务更完善。
所以我的结论是:
- 中小型项目、创业公司、MySQL人才多的团队 → MySQL完全够用,别折腾
- 数据量大、查询复杂、需要强一致性和高并发 → PostgreSQL更香
- 数据模型不固定、业务逻辑经常变 → PostgreSQL的JSONB + 关系混合能力甩开MySQL
- 离不开阿里云全套服务 → 硬着头皮用MySQL吧,别跟自己过不去
结尾
回到开头那个故事。被开除之后,我去了新公司,技术选型还是我拍板。这次我没那么冲了——先做POC,用数据说话。
结果?PostgreSQL跑了两年,没出过数据问题,查询性能一直稳定。那些当初反对的同事,现在代码写得飞起,没人再提MySQL的事了。
技术选型不是宗教战争,是解决实际问题。用什么不重要,能不能驾驭才重要。但如果你问我:下次让我选,我选什么?
PostgreSQL。不是因为它完美,而是因为在我踩过的坑里,它让我少踩了一些。
就这样。