写这篇mysql与postgresql对比,起因是我在某个中等规模的项目里做数据库选型,前期拍脑袋选了MySQL,后期被复杂统计查询和JSON处理折磨得不行,才回头认真把PostgreSQL摸了一遍。那次切换让我意识到,很多人讨论“MySQL还是PostgreSQL”时,根本不是在同一个维度上比较,有人只看单机写入性能,有人看功能丰富度,有人看运维习惯,最后吵不到一块去。
这篇文章我不打算报参数跑分,那类数据你随时能搜到,而且跟你的业务负载关系不大。我要做的是把两者的底层逻辑、适用场景、迁移实操、常见坑一次讲透,用我实际踩过的案例来说明“什么时候该用谁”。如果你是做后端开发、DBA或者独立开发,正好在纠结数据库选型,这篇可以直接当参考清单用。
1. 为什么需要这份对比:选型从来不是比跑分
1.1 一次真实的选型失误复盘
之前接了一个偏数据展示类的项目,业务形态是有大量用户上报行为数据,后台要按多种维度做聚合统计,同时还有一部分事务型操作,比如用户信息修改、配置更新。刚开始图省事,直接选了MySQL,理由是团队熟、网上的教程多、云厂商默认就给这个。结果业务跑到中期,几个问题冒出来了。
第一个问题是复杂统计SQL越来越难写。MySQL在5.7和8.0版本里已经加了窗口函数,但整体的分析函数生态还是没有PostgreSQL完整,很多查询需要拆成多条SQL在应用层做二次聚合,慢不说,代码还脏。第二个问题是JSON处理。当时有一部分业务数据是半结构化的,MySQL 5.7虽然支持JSON类型,但索引和查询语法都比较别扭,尤其是需要按JSON字段内部做条件过滤的时候,性能掉得厉害。第三个问题是递归查询,有一张组织架构表需要做层级汇总,MySQL 8.0之前没有WITH RECURSIVE,只能靠应用层循环查数据库,N次往返延迟看着就头疼。
后来把核心统计库迁到了PostgreSQL,同一套查询逻辑,SQL简洁了不止一倍,性能还更好。这个反差让我意识到,MySQL和PostgreSQL的差距不是简单的“谁快谁慢”,而是设计哲学和使用场景的错位。
1.2 这份对比解决的核心问题
很多人选型问的是“MySQL性能是不是比PostgreSQL好”,这个问法本身就是陷阱。数据库没有绝对快慢,只有适合不适合。真正要搞清楚的是这几点:
- 你的业务是OLTP为主还是OLAP为主,还是混合负载。
- 你需要怎么样的SQL表达能力,简单CRUD还是复杂窗口、递归、CTE、JSON查询。
- 你的并发写入模型是什么样的,高并发小事务,还是大批量导入。
- 你的运维团队更熟悉哪套体系,备份、监控、高可用方案是否成熟。
- 你的成本模型是什么,云数据库RDS价格差异、自建服务器资源占用。
这篇文章就是把上面这些问题一个个拆开,告诉你MySQL和PostgreSQL在每一项上的真实差异,以及落到项目里该怎么权衡。说白了一句话:别被舆论带着跑,看清楚自己的业务再选。
1.3 适合读这篇对比的人
我自己把读者分成三类。第一类是刚入门的新手,对数据库的理解停留在“建表、增删改查”,需要有人告诉他两个数据库到底差在哪,为什么社区天天吵。第二类是主力开发,正在做技术选型,需要一个相对完整的决策框架,避免像我那样中期迁移。第三类是DBA和运维,需要了解迁移过程中容易踩的坑,以及日常巡检和维护的差异点。
这三个角色关注的点不一样,但底层逻辑是相通的。我先从两者的底层差异讲起,再讲性能体现,最后落到实操迁移和排查,你可以按需跳读,但我建议还是顺着看一遍,因为很多坑是连着的。
2. MySQL与PostgreSQL底层逻辑的六大核心差异
2.1 存储引擎:插件化与单一架构
MySQL最著名的特点就是存储引擎插件化,InnoDB、MyISAM、Memory、NDB各管一摊。早期MyISAM还在用表级锁,崩溃恢复也差,后来InnoDB成了默认引擎,支持事务、行级锁、崩溃恢复,大家才逐步统一。但在一个MySQL实例里,理论上可以同时存在多个存储引擎,不同表用不同引擎,甚至某些引擎的问题会牵扯到全实例。
PostgreSQL没有存储引擎的概念,它的核心直接管理表存储,所有表和索引都由同一种堆表结构支撑。你不需要选引擎,也不会出现“这张表忘了设置成InnoDB结果事务失效”的低级问题。加上PostgreSQL的VACUUM机制处理bloat,它是通过多版本并发控制加定期清理来保证性能和空间回收。
这个差异带来的实际影响是:MySQL的灵活性高,但使用不当的坑也多;PostgreSQL的约束性强,但默认行为更统一,不容易犯低级错误。团队如果是新手居多,PostgreSQL反而更友好。
2.2 SQL标准与语法细节:谁更“正统”
我见过不少从MySQL转PostgreSQL的人,第一反应是“怎么什么都要写CAST,字段类型转换咋这么严格”。没错,PostgreSQL对类型的要求非常严格,不会帮你做太多隐式转换,而MySQL在类型转换上宽松得多。比如把字符串和数字比较,MySQL会隐式把字符串转数字,PostgreSQL大概率直接报错或者需要你显式转换。
严格和宽松各有各的道理。MySQL的宽松,是方便使用者,写出看似简洁的SQL;PostgreSQL的严格,是逼你明确语义,避免线上出现诡异的数据比较结果。从我后期的运维体验看,PostgreSQL的严格在长期维护上是省心的,因为很多脏数据问题在SQL执行阶段就被拦住了。
SQL功能丰富度方面,PostgreSQL明显更完整。它的数组类型、范围类型、JSONB、全文检索、窗口函数、CTE、递归查询、LATERAL JOIN,基本是开箱即用。MySQL 8.0虽然在补齐这些特性,但整体的成熟度还是有差距,比如JSONB的GIN索引和查询操作符,PostgreSQL已经打磨了很多年,MySQL的JSON索引到现在使用限制仍然不少。
2.3 索引机制:B+树之外的更多选择
MySQL InnoDB的索引主要是B+树,主键聚簇索引,二级索引需要回表,全文索引走的是倒排索引,空间索引是R-tree,但整体使用上,大家日常主要接触的就是B+树。PostgreSQL的索引类型就丰富很多,B-tree只是其中之一,还有Hash、GiST、SP-GiST、GIN、BRIN等。
GIN索引是处理JSONB、全文检索、数组的利器,很多复杂查询就靠它提速。BRIN索引适合超大表的顺序数据,比如时间序列,索引大小可以做到非常小,扫描大表时能大幅减少I/O。GiST则可以用来做地理位置的近邻搜索、范围排除等操作。
这意味着如果你的业务有非B+树结构的查询需求,PostgreSQL的索引类型可能直接改变查询方案设计。比如我们当时用了GIN索引处理JSONB过滤和全文检索,效果非常明显。MySQL用不上这些,不是MySQL不够好,是它的索引生态更聚焦在传统关系型场景。
2.4 事务与MVCC实现:读写冲突的处理差异
MySQL InnoDB的MVCC是将旧版本数据存储在undo log中,数据行上有隐藏的DB_TRX_ID和DB_ROLL_PTR。读操作走快照读,写操作需要加锁,提交后版本更新。PostgreSQL的MVCC更激进,它会将旧版本数据行直接保留在数据页里,通过xmin/xmax系统列判断可见性,更新等于插入新版本,旧版本靠VACUUM垃圾回收。
这个机制带来的现象是:PostgreSQL的UPDATE会产生死元组,如果VACUUM跟不上,表会膨胀,查询性能下降,这也是PostgreSQL最常被吐槽的点。但反过来,PostgreSQL的读不会阻塞写,写不会阻塞读,MVCC实现得比MySQL更干净。MySQL InnoDB的读写阻塞在特定隔离级别下会有不同表现,比如RR下的间隙锁问题,容易引发死锁。
实践上看,高并发小事务场景两者都能扛住,但如果你的业务有频繁UPDATE和并发读混合,PostgreSQL需要更重视自动VACUUM参数的调优,否则膨胀问题会很难看。
2.5 复制与高可用方案
MySQL的复制生态非常成熟,主从复制基于binlog,支持异步、半同步和组复制,搭建MySQL高可用相对容易。因为MySQL在互联网公司用得多,各种高可用中间件、云数据库方案都很完善。
PostgreSQL的复制基于WAL,支持同步和异步流复制,逻辑复制在10.0版本以后逐渐可用。物理复制做得也很好,但是高可用生态相对分散,没有MySQL那么统一的“大家默认这么搞”的方案,经常遇到Patroni、repmgr、etcd等一堆组件拼装。
如果你在一个已经有了成熟MySQL运维体系的团队,迁移到PostgreSQL最痛苦的可能不是数据库本身,而是你原有的监控、备份、高可用脚本全部要重写。这个隐性成本经常被人忽略。
2.6 JSON与半结构化数据处理
现在业务里带点JSON太常见了,用户扩展字段、配置快照、埋点参数。MySQL 5.7开始支持JSON类型,8.0支持JSON_TABLE等JSON函数,但MySQL的JSON实际存储是二进制JSON,支持索引的方式是通过生成列加索引,查询JSON内部字段必须依赖expression index,路径表达式的功能和PG的JSONPath相比还是有差距。
PostgreSQL的JSONB是把JSON解析成二进制格式存储,支持GIN索引直接索引内部字段和元素,查询可以用@>、?、?|、?&等操作符,语法骨骼清奇但能力很强。JSONB还支持对字段的追加、删除、拼接等操作,处理半结构化数据特别顺手。
我们当时在MySQL里查JSON字段里的某个值,要先建生成列再建索引,改表结构的过程还容易锁表。迁到PostgreSQL后,一个GIN索引搞定,查询直接写JSONB操作符,体验差距非常大。但如果你的JSON只是存着偶尔读一下,从不按内部字段过滤,MySQL也够用,没必要为了JSON而迁移。
3. 性能实测与业务场景匹配:哪些场景选谁更合理
3.1 简单CRUD小项目:MySQL的舒适区
如果你的项目就是用户表、订单表、商品表,主要是单表增删改查,最多做几个表关联,MySQL和PostgreSQL的体感差异几乎可以忽略。两者在这个场景下都能扛住上万TPS,瓶颈通常在应用层不在数据库。
这种情况下选择MySQL更多是出于生态习惯。团队熟悉,网上答案多,云数据库便宜,出了问题拉个群问一句一堆人回。PostgreSQL在这个场景没有明显的优势,还要承担团队学习成本。
不过真要说极端情况,PostgreSQL的并发读能力其实比MySQL耐压,很多压测工具在纯读场景下PG的曲线更平缓,但这属于量级很大的时候才能体现。小项目阶段,不用管这个。
3.2 复杂分析查询:PostgreSQL明显占优
这是两者差距最大的一块。PostgreSQL的优化器在复杂join和子查询的处理上比MySQL成熟,尤其是多表关联、窗口函数、CTE、递归这些场景。MySQL 8.0的优化器进步很大,但很多时候还是要靠hint或者改写SQL才能跑出理想性能。
举个例子,当时我们要统计一组用户在过去30天内的行为漏斗,涉及事件表、用户表、渠道表三张表,还要按天做聚合和窗口排序。MySQL版本写出来是一条超级长的SQL,执行计划不是全表扫就是用的索引不理想。切到PostgreSQL后,同样的逻辑用CTE加窗口函数拆成几段,优化器自己选了比较合适的join顺序,查询时间降了一个数量级。
如果你的业务有大量报表、数据分析、复杂过滤,PostgreSQL应该是优先考虑的。MySQL不是不能做,是做得不够爽,开发和调优成本高。
3.3 高并发写入:取决于写入模型
有一个流传很广的说法是“MySQL写入比PostgreSQL快”,这个说法过于绝对。MySQL的InnoDB在高并发小事务写入上确实很能打,因为它的redo log机制和组提交优化做得比较到位。PostgreSQL的WAL日志机制早期fsync设置比较保守,性能上吃亏,但通过调整synchronous_commit、wal_buffers、checkpoint参数,差距能大幅缩小。
真正拉开差距的场景是大批量插入。PostgreSQL的COPY命令做批量导入是非常强的,比逐条INSERT快很多倍。MySQL虽然有LOAD DATA INFILE,但遇到数据里有特殊字符、字段顺序变化时,容错能力不如COPY灵活。
还有一点是死锁问题。MySQL在RR隔离级别下间隙锁引发死锁的概率更高,PostgreSQL的锁粒度控制更细,冲突场景相对少。如果你的表结构简单、主键明确、写入模式单一,两者都没问题;如果写入模型复杂,带大量范围更新和并发事务,PostgreSQL的毛刺会更少。
3.4 地理位置与全文检索:PostgreSQL的杀手锏
PostGIS是PostgreSQL最惊艳的扩展之一,把关系型数据库硬生生变成了专业GIS数据库。如果业务涉及到经纬度计算、地理围栏、路径规划,用PostGIS的效率和开发成本远低于自己在MySQL里写球面距离公式。
全文检索方面,PostgreSQL内置了tsvector和tsquery,支持中文分词的插件也不少,可以直接做轻量级的搜索服务。MySQL的全文检索默认只支持英文分词,中文分词要么靠ngram解析器,要么接第三方搜索引擎。如果你不想引入Elasticsearch,又想做一个百级万级别数据量的站内搜索,PostgreSQL是性价比高得多的选择。
3.5 云数据库与运维成本:现实约束
现在很多人并不是自建数据库,而是直接用云数据库,那选型还会受商业化影响。云厂商的MySQL产品都非常成熟,各种规格、只读实例、备份恢复、监控告警都是标配,价格也相对低。PostgreSQL的云产品这几年也起来了,但部分云厂商对PG的运维插件和高可用方案的整合度不如MySQL,偶尔会有版本滞后的问题。
如果你是从零起步,没有历史包袱,我更推荐认真评估PostgreSQL,因为它能覆盖的业务场景更广,长期看能省掉不少额外组件的钱。如果你所在团队已经有成熟的MySQL运维体系和自动化平台,那继续用MySQL完全没问题,尤其公司里存了大量历史MySQL经验,迁移成本可能远大于功能收益。
4. 迁移实操:从MySQL切到PostgreSQL的完整流程
4.1 迁移前的准备工作
迁移不是把数据倒过去就完了,要先做兼容性评估。我当时做了三件事。第一,列出所有表和索引,确认字段类型和数据量,排查有没有MySQL特有类型,比如ENUM、SET、TINYINT(1)这种。第二,梳理应用层SQL,重点看有没有MySQL特有的语法,比如REPLACE INTO、INSERT ... ON DUPLICATE KEY UPDATE、GROUP BY的隐式排序、concat函数的多参数写法。第三,确认业务对停机时间的要求,决定是停机迁移还是增量迁移。
对于PostgreSQL的字段类型映射,MySQL里的TINYINT、SMALLINT、INT、BIGINT可以对应PG的SMALLINT、INTEGER、BIGINT,但注意PG的INT就是4字节,没有TINYINT的概念,如果你需要极小的整型存储,PG没有对应的1字节类型,MySQL的TINYINT转换后通常就用SMALLINT代替,代价是多占一个字节。DATETIME对应TIMESTAMP,MySQL的TIMESTAMP有2038年问题,PG的TIMESTAMP范围大得多。ENUM类型建议直接改成TEXT加CHECK约束,SET类型可以改成数组或者关联表。
4.2 数据迁移方式选择
数据迁移我推荐直接用工具,别自己写脚本。最常用的方案是pgloader,它能直接从MySQL读取表结构并映射到PostgreSQL,同时做数据搬移,还能生成转换报告。pgloader对大部分常规类型的处理都很好,但碰到特殊类型、不可见字符、超大字段时还是需要先清洗。
如果数据量很大,pgloader可能会比较慢,可以用分表导出CSV,然后通过PostgreSQL的COPY命令导入。导出时要注意MySQL的字符集,特别是UTF8MB4和UTF8的差异,PostgreSQL的UTF8对应的是完整的Unicode,基本不用太担心。导入前建议先把目标表的索引和外键全部去掉,导入完成后再一次性重建,否则每插入一行都要维护索引,速度会慢很多。
4.3 切换过程中的常见兼容性坑
第一个坑是auto_increment和serial。MySQL的AUTO_INCREMENT在PG里对应SERIAL或IDENTITY列,建表的时候要手动改。更坑的是,某些ORM会在插入时显式传ID,如果序列没有同步,会出现主键冲突。迁移后必须把序列的当前值设置成原表的最大ID,否则新插入的数据会撞主键。
第二个坑是update join语法。MySQL的UPDATE多表关联有自己的写法,比如UPDATE a JOIN b ON a.id=b.id SET a.x=b.y,PostgreSQL不支持这种语法,要用UPDATE a SET x=b.y FROM b WHERE a.id=b.id。DELETE也是类似的,MySQL支持DELETE a FROM a JOIN b,PostgreSQL要用USING。
第三个坑是Boolean类型。MySQL的TINYINT(1)经常被当作BOOL用,但它的值可以是0、1甚至更大,PG的BOOLEAN只有true、false,严格模式下一转换就可能出问题。建议应用层迁过来后统一把布尔语义梳理一遍。
第四个坑是GROUP BY。MySQL默认允许SELECT非聚合列且不报错,PG要求所有非聚合列都必须出现在GROUP BY里。这个问题在迁移早期会暴露一大片SQL报错,提前告诉开发同学,让他们统一改掉,比线上报错再救火舒服得多。
4.4 迁移后的参数调优
迁移完成后不要急着把默认配置扔到生产。PostgreSQL有几个参数值得重点关注。shared_buffers建议设置到物理内存的25%左右,但别太高,因为还有操作系统缓存和各个进程的内存。work_mem是每个排序和哈希操作可用的内存,建议先调成64MB左右,但这个参数是每个操作都会分配,并发高的时候内存可能吃紧,要看实际情况。maintenance_work_mem影响VACUUM和建索引的速度,数据量大的时候可以调到512MB甚至更高。
checkpoint相关参数也很重要。max_wal_size建议调到2GB以上,减少checkpoint频率。wal_level如果是逻辑复制场景要改成logical,否则配好replica即可。synchronous_commit如果业务能接受极少量数据丢失风险,可以设成off,写入性能提升非常明显,但前提是你要清楚这个取舍的代价。
5. 常见问题排查与避坑清单
5.1 PostgreSQL表膨胀问题排查
PostgreSQL最经典的坑,就是UPDATE多了之后表体积明显增大,查询没变慢多少,但磁盘和备份占用越来越大。VACUUM机制不是即时腾出空间,而是把可回收的元组标记为死元组,允许空间被后续插入复用,但如果插入模式不均匀,重新复用效率很低。
排查方式很简单,查pg_stat_user_tables里n_dead_tup和last_autovacuum。如果n_dead_tup长时间维持在高位,说明自动VACUUM没跟上,或者表太大导致VACUUM时间拉长。解决办法是调低autovacuum_vacuum_threshold和autovacuum_vacuum_scale_factor,对大表可以单独配置。如果已经膨胀得厉害,可以用pg_repack重建表,这个操作能在线完成,不需要长时间锁表,但要在低峰期跑。
MySQL里对应的坑其实也有,InnoDB的索引碎片和undo log膨胀同样会让表变大,但大家平时对MySQL这类问题关注度不如PG那么高,因为MySQL的工具链更成熟,一键优化就行。
5.2 连接数打满怎么办
MySQL的max_connections默认是151,PG默认是100,两者在小配置下都容易被打满。Connections异常飙升时,第一件事是查执行中的SQL,定位是不是有慢查询把连接占住了,而不是无脑把max_connections调大。PG里可以查pg_stat_activity,MySQL查performance_schema或者information_schema.processlist。
这里有个经验,数据库连接数尽量不要单纯靠扩容解决,接入层应该做连接池。业务侧可以用中间件或者ORM自带的连接池,DBA侧可以用PgBouncer或者ProxySQL。我见过很多团队把连接池设到非常大会话数,然后数据库内存被连接数吃光,实际上是拿内存换连接,不划算。
5.3 慢查询分析差异
MySQL的慢查询日志很成熟,long_query_time一设,日志一条不落,还有mysqldumpslow等分析工具。PG的慢查询分析稍微绕一点,最常见的是开pg_stat_statements扩展,它会记录每条SQL的调用次数、总耗时、平均耗时和缓冲命中情况。事前要记得在shared_preload_libraries里加上pg_stat_statements,否则要重启数据库才生效。
分析慢查询的思路是类似的,先看总耗时排序,再点击查看执行计划。PG有EXPLAIN ANALYZE,MySQL也有EXPLAIN ANALYZE(8.0.18后),能把每一步实际执行时间打出来。重要提醒是EXPLAIN ANALYZE会真实执行语句,线上环境一定要确认语句是只读的,否则可能产生数据变更。
5.4 高可用切换的差异
MySQL的故障切换在大多数团队已经有比较成熟的实践,基于半同步再加MHA或者Orchestrator之类的方案,切换脚本通常已经验证过很多次。PG的高可用切换在Patroni方案里比较常见,它维护一个分布式共识集群,选主逻辑很清晰,但组件多,排查起来要很熟悉整套链路。
我个人的建议是,如果原来团队全是MySQL出身,硬切到PG后高可用部分一定要额外做演练。数据库迁移不只是版本迁移,运维体系的迁移才是最容易出事故的部分。一定要在测试环境模拟主机宕机、主从切换、数据回追,反复几轮再上生产。
5.5 备份恢复的坑
MySQL的备份工具mysqldump和XtraBackup分工清晰,物理备份用XtraBackup,逻辑备份用mysqldump,大家都很熟悉。PG的pg_dump是逻辑备份,物理备份需要用到pg_basebackup和WAL归档。最容易被忽略的是PITR(时间点恢复)的配置,很多团队只做了备份没做WAL归档,结果真出事只能恢复到备份点,丢了半天的数据。
如果预算允许,云数据库的自动备份通常已经帮你把这块兜住了,自建的话一定别省WAL归档的配置。我当时迁PG的时候,第一时间就在测试环境完整做了一次“备份加恢复”演练,确认恢复时间和恢复目标清晰,才敢把生产数据切过去。
6. 避坑清单与选型决策建议
6.1 什么时候坚定选MySQL
- 团队现有技术栈和运维体系深度绑定MySQL。
- 业务是典型的电商、CMS、论坛等结构规整的OLTP。
- 并发模型简单,不需要复杂SQL和高级索引功能。
- 云厂商的MySQL产品已经满足可用性和成本要求。
这类场景硬上PostgreSQL反而增加风险,没有收益。
6.2 什么时候坚定选PostgreSQL
- 业务涉及复杂报表、多维度聚合、数据分析。
- 大量使用JSON和半结构化数据,且需要按内部字段过滤和索引。
- 需要全文检索、地理信息、递归查询等高级功能。
- 希望减少中间件依赖,一个数据库搞定更多事情。
这类场景选PostgreSQL能省掉不少应用层代码和相关组件的维护成本。
6.3 什么时候需要混合使用
如果业务体量足够大,单一数据库不可能满足所有需求。不要抱着“一个数据库走天下”的执念,合理的架构是让MySQL承担简单高频的OLTP,把分析类场景交给PostgreSQL或者专门的数据仓库。两个数据库之间通过数据同步工具或者消息队列做数据流转,各取所长,这个方案在实践中是能长期稳定跑下去的。
选择混合方案会增加基础设施复杂度,需要有明确的理由,比如单纯因为“我想用PG的某个功能”去引入一套新存储,是很不值得的。
7. 最后聊几句实在经验
数据库选型这件事,最忌讳的就是只看社区热度或者别人一句“PG更先进”。我在几个项目里的实测体会是,如果你的团队能接受一定程度的学习成本,PostgreSQL在功能上限和长期扩展性上明显更好;但如果你的团队没有专职DBA,运维经验全靠攒,继续用MySQL也是稳妥且务实的选择。
真要我给一个具体的判断路径,就是在项目启动前花两三天做一轮概念验证,拿真实业务的表结构和SQL去跑,把执行计划拉出来看看,而不是听任何人下结论。我自己那次的迁移虽然折腾,但MySQL和PostgreSQL都摸过一遍之后,再遇到数据库选型问题心里就有底了。希望能帮你少踩几个我之前踩过的坑。