MySQL 千疮百孔,为什么没人能替代它

MySQL 的问题不在于它没有坑,而在于它已经成了太多团队的默认值。工程世界里,最难替代的从来不是软件,而是习惯、经验和风险定价。

MySQL 千疮百孔,为什么没人能替代它

技术评审会上,最尴尬的不是没人知道 MySQL 有坑。

最尴尬的是,所有人都知道它有坑,最后还是选它。

有人说 PostgreSQL 更严谨,有人说 MySQL 的外键、触发器、枚举、事务边界都要小心,有人甚至能现场背出几个历史遗留问题。会议开到最后,大家把迁移成本、线上经验、云厂商支持、团队熟练度、历史 SQL、ORM 方言、排障手册一摊开,气氛就变了。

问题从“它好不好”,变成“出了事谁负责”。

这才是 MySQL 最有意思,也最残酷的地方。

它当然不完美。甚至可以说,它身上有一堆非常不优雅的历史包袱。但它仍然是开源关系数据库世界里最强的默认选项之一。MySQL 官网到今天还把自己称为 “The world’s most popular open source database”,DB-Engines 这类榜单上,它也长期站在最靠前的位置。

所以真正值得问的,不是“MySQL 到底烂不烂”。

这个问题太容易变成情绪表态。

真正值得问的是:一个到处有补丁、有边界、有反直觉行为的软件,为什么还能变成事实标准?

MySQL 赢的不是优雅,而是默认值。

坑不是最可怕的,悄悄返回结果才可怕

数据库最让人害怕的 bug,通常不是直接报错。

报错至少诚实。它告诉你:这里不行,停下来处理。

更危险的是那种看起来能跑、能查、能返回结果,但行为和你脑子里的模型不一致的东西。你以为自己在和一个严肃的系统打交道,结果它在某些地方像脑筋急转弯。

比如 ENUM。

很多团队会用它表示状态、尺码、等级:

1
2
3
CREATE TABLE products (
  size ENUM('S', 'M', 'L', 'XL')
);

按直觉,SMLXL 是一组字符串。你排序的时候,也许会以为它们会按字母顺序走。但 MySQL 官方文档明确说,ENUM 的排序默认基于它在定义里的索引位置。也就是说,ENUM('b', 'a') 里,b 会排在 a 前面。

这不是没人告诉你。文档里写了。

问题是,业务开发者每天脑子里装的是订单状态、库存、优惠券、用户权限,不是每个数据库类型的历史脾气。一个字段明明长得像字符串,很多时候也像字符串一样被使用,但在排序时又按定义顺序来。你当然可以说“这是文档行为,不是 bug”。

可工程里的危险,恰恰经常藏在这种地方。

它不是错到跑不起来,而是对到需要你记住一条额外规则。

规则一多,团队就会变聪明。

然后聪明人会说:少用 ENUM。

Trigger 的问题,是它动摇了“我以为”

更典型的是 trigger。

触发器本来是一个很好理解的东西:某张表发生 INSERT、UPDATE、DELETE,我让数据库自动做一件事。比如记审计日志、同步状态、更新计数。

直觉上,只要这张表的数据被改了,trigger 就应该触发。

但 MySQL 官方文档里有一句非常硬的说明:

1
Cascaded foreign key actions do not activate triggers.

级联外键动作不会激活 trigger。

这句话的杀伤力不在术语,而在直觉。

假设你有一个员工表,里面有部门 ID。部门被删除后,外键规则把员工表里的 department_id 设为 NULL。数据确实变了,行也确实被改了。但这个修改不是你直接 UPDATE employees 产生的,而是外键级联动作带来的。

如果你的审计逻辑依赖员工表上的 trigger,你可能会以为这次变化也会被记录。

MySQL 说:不会。

这不是我脑补的案例。MySQL Bug #11472 早在 2005 年就被提交,标题就是 Triggers not executed following foreign key updates/deletes。原始报告里,删除父表记录后,子表外键列被设为 NULL,但 after update trigger 的计数仍然是 0;直接更新子表时,trigger 又能正常触发。

更讽刺的是,页面后续评论里出现过“我们会在 5.1 修复”之类的说法。多年之后,这件事更像是被文档化成了行为边界。

你当然可以说:既然文档写清楚了,那就按文档来。

没错。

但一个功能一旦需要你不断提醒自己“这里不是你以为的那个语义”,它就不再只是功能。它变成了团队规范、代码评审提醒、事故复盘和新人培训的一部分。

最后大家会得到一句更短、更容易传播的话:

Trigger 别用。

这句话未必公平,但很有效。

为什么这么多坑,大家还是不走

到这里,很容易得出一个痛快结论:既然 MySQL 这么别扭,那就换 PostgreSQL。

我也喜欢这个结论。

它干净,正义,听起来很懂数据库。

可工程世界不是审美投票。工程世界更像一张债务表:你每做一个选择,都在未来某个地方付利息。

MySQL 最大的护城河,不是它没有替代品。

恰恰相反,替代品很多。PostgreSQL 更严谨,扩展能力更强,SQL 语义更舒服;SQLite 在嵌入式和本地场景里非常漂亮;云厂商也在推各种兼容层、新型分布式数据库、Serverless 数据库。

但替代品存在,不等于替代会发生。

因为你要替代的从来不只是一个数据库进程。

你要替代的是:

  • 团队十几年攒下来的排障经验;
  • 一堆没人敢动的历史 SQL;
  • DBA 和后端之间形成的默契;
  • 云服务、监控、备份、迁移、审计的整套工具链;
  • 面试、教程、外包团队、开源项目默认支持的生态;
  • 以及最麻烦的东西:出了事故时,大家知道该找谁、该看哪、该怎么止血。

MySQL 难以替代的不是一个进程,而是一整套风险网络

技术选型里有个常被低估的事实:

最难替代的不是软件,是习惯。

一个团队用 MySQL 用了很多年,它会形成自己的安全区。哪些功能别碰,哪些 SQL 慢,哪些版本有坑,哪些参数要调,哪些告警不用慌,哪些错误一出现就该先看连接池。

这些东西不优雅,但值钱。

PostgreSQL 可能在很多地方更好。可一旦你真要迁移,问题就不是“新数据库好不好”,而是“我们有没有能力把旧世界里的所有隐性知识搬过去”。

很多团队没有。

或者说,有,但不值得。

事实标准通常不是最好用出来的

技术圈有个浪漫误会:好技术会赢。

现实通常没这么体面。

真正赢下来的技术,常常是刚好出现在正确时间、绑定了正确生态、足够便宜、足够容易部署、足够多人会用,然后一路靠惯性滚大。

MySQL 早期吃到了 Web 爆发、LAMP 生态、虚拟主机、开源教程和低门槛部署的红利。对大量网站和业务系统来说,它不是被严肃比较后胜出的,它是“默认就在那里”。

默认值的力量很恐怖。

默认值会进入教程。教程会进入面试。面试会进入招聘。招聘会影响团队能力模型。团队能力模型又会反过来决定下一次选型。

这条链路一旦闭合,一个技术就不再只是技术了。

它变成行业语言。

这也是为什么很多 MySQL 的坑没有真的把它赶下桌。因为用户不是每次都在问“这是不是最优雅的数据库”。更多时候,他们问的是:

这套东西我们能不能招到人维护?

云厂商有没有成熟服务?

线上出了问题,搜索引擎能不能找到答案?

我们现有系统迁过去,要停多久?

老板问风险,谁敢签字?

这些问题听起来没那么技术,但它们决定了技术命运。

数据库选型不是审美投票,是风险定价。

MySQL 的真正危险:把妥协伪装成常识

所以我不想把这篇写成“MySQL 垃圾,大家快逃”。

这太省事,也太不负责任。

MySQL 能用。大量业务系统也确实用得很好。很多团队选择 MySQL,并不是愚蠢,而是现实:项目规模不复杂,团队熟悉,云服务成熟,工具链齐全,成本可控。

这叫工程判断。

真正危险的是另一种东西:把针对 MySQL 的妥协,包装成数据库世界的普遍真理。

比如:

“数据库别管太多,业务逻辑都放应用层。”

“外键没必要,应用代码保证就行。”

“Trigger 都是坑,懂行的人不用。”

“数据库就是存数据,别搞那些花活。”

这些话有时是对的。复杂 trigger 确实会制造隐式副作用;滥用外键确实会让写入路径变重;把业务逻辑全塞进数据库,后期维护也可能很痛苦。

但它们不应该变成宗教。

如果你不用外键,是因为你评估过当前业务、当前团队、当前数据库实现的代价,那没问题。

如果你不用外键,只是因为“我们一直这么干”,那就要警惕了。

你以为自己在做最佳实践。

也许你只是在复读旧伤口。

MySQL 最麻烦的地方,不是它有坑,而是它的坑太普及了。普及到很多人已经分不清:哪些是关系数据库本来就该有的边界,哪些只是 MySQL 训练出来的恐惧。

不要轻易换,也不要轻易信

那到底该怎么办?

我的答案可能不够爽:不要因为讨厌 MySQL 就迁移,也不要因为大家都用 MySQL 就停止怀疑。

如果你在做新项目,团队又有足够能力,认真评估 PostgreSQL 是很值得的。它在类型、约束、事务、索引、扩展能力上,确实给了开发者更多可以信任的东西。

如果你在维护一个已经跑了多年的 MySQL 系统,也别因为几篇文章就热血上头。迁移数据库是大手术,不是换编辑器主题。你要评估数据规模、停机窗口、SQL 方言、索引差异、事务语义、监控备份、回滚预案,还要评估团队有没有足够多的人真的理解新数据库。

更现实的做法,是先把自己的判断从 MySQL 的默认值里拆出来。

下次团队里再有人说“这个功能别用”的时候,我建议先问四个问题:

  1. 我们不用它,是因为这个能力本身危险,还是因为 MySQL 的实现让它危险?
  2. 如果约束不放数据库,应用层靠什么保证?代码、流程,还是大家记得?
  3. 这个经验离开 MySQL 还成立吗?换 PostgreSQL、SQLite、SQL Server 后还成立吗?
  4. 我们是在做工程取舍,还是在把历史创伤讲成技术真理?

这四个问题,比“支持 MySQL 还是支持 PostgreSQL”更有价值。

因为真正成熟的技术判断,不是骂一个工具,也不是崇拜另一个工具。

而是你知道自己在为什么付钱。

MySQL 千疮百孔,但它不会很快消失。

工程世界里,坏东西不会因为坏就消失。它只会在足够便宜、足够熟悉、足够多人会修的时候,继续活得很好。

所以别把 MySQL 的流行误读成完美。

也别把替代 MySQL 想得太轻。

一个系统最难换掉的部分,往往不是代码,也不是数据。

是人脑里那套默认设置。

如果你喜欢这种不只看热闹、也看工程代价的技术观察,可以关注我。下一次我们继续聊那些“明明不完美,却很难被替代”的东西。