ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从MySQL迁移到PostgreSQL:选型、性能对比与避坑实操指南

从MySQL迁移到PostgreSQL:选型、性能对比与避坑实操指南 开头这两年国内技术圈有个挺有意思的变化——以前大家聊数据库选型MySQL几乎是默认答案新项目上来先问“用哪个版本”老系统要改造也只会在MySQL内部折腾。但最近一年我明显感觉到风向变了越来越多的团队在认真评估PostgreSQL有些大厂甚至把核心业务系统从MySQL迁了过去。朋友圈里“为什么弃坑MySQL转投PostgreSQL”这类讨论高频出现社区里PG的帖子点赞数也明显涨上去了。这篇东西就聊聊我对这件事的观察MySQL到底哪里让人不满意PostgreSQL凭什么接得住这些场景以及真到了迁移这一步实际操作中你会踩到哪些坑、需要做什么准备。不管你是还在选型阶段的架构师还是已经在评估迁移的DBA、后端开发这篇应该都能给你一些可以直接拿去用的参考。1. 先搞清楚一件事大家“弃坑”MySQL弃的到底是什么1.1 MySQL的黄金时代与今天的核心痛点MySQL能火这么多年不是没道理的。它轻量、好用、文档多LAMP时代攒下的生态底子太厚了直到今天很多中小团队的首选依然是它。我早期做项目也基本是MySQL一把梭分库分表、主从复制、读写分离一套组合拳下来就能扛住不小的流量。但问题也出在这里。MySQL发展到今天它的一些底层设计是带着时代烙印的早年没问题数据量和业务复杂度上来以后就慢慢变成约束了。最典型的一个点是SQL标准完成度。MySQL的SQL语法在很多地方是“能用但不够标准”的窗口函数到8.0才正式可用CTE支持得也晚FULL OUTER JOIN到现在还不支持递归查询写起来别扭JSON处理能力相比PostgreSQL简直是两个时代的东西。另一个痛点就是存储引擎的分层设计。MySQL的InnoDB和MyISAM是分开的早年用MyISAM的人多因为读快但表锁、崩溃恢复差后来大家又整体切到InnoDB。这种“引擎之争”本身说明一个问题MySQL在设计上更强调灵活性和易用性但在一致性、完整性这些关系数据库的核心能力上它是做了取舍的。而PostgreSQL从一开始就是奔着“最先进的开源关系数据库”去的SQL标准完成度、数据类型丰富度、约束和触发器能力、扩展机制都是直接对标商业数据库的。1.2 PostgreSQL到底是什么定位PostgreSQL经常被说成“开源界的Oracle”这话其实挺准确的。它有完整的事务支持和ACID保障有非常强的SQL标准兼容能力有丰富的数据类型包括JSON/JSONB、数组、范围类型、网络地址类型等原生支持。它的扩展机制也特别猛——不是普通的插件概念而是可以直接改变数据库行为的那种比如PostGIS直接把数据库变成空间数据库TimescaleDB能把它变成时序数据库。我还记得第一次用PG的JSONB时的那种感觉MySQL存JSON就是个美化版的TEXT偶尔取个字段还得靠JSON_EXTRACT函数性能一般语法也啰嗦。PG的JSONB是索引友好的支持GIN索引可以直接用、?这些操作符做高效查询还能在JSONB字段上建表达式索引。对很多要存半结构化数据的业务来说这个能力直接把“要不要上NoSQL”的讨论干掉了。1.3 大厂转向的几个实际信号说“大厂转投”其实不完全是一刀切搬家。我看到的情况更多是分层推进新建业务优先用PG老系统逐步迁移或者对MySQL的老大难场景单独评估PG方案。比如一些被MySQL的分库分表复杂度逼疯的团队发现PG单机就能扛住以前要拆八个库才能扛住的读写量运维模型直接简化一大截。另一个容易被忽略的原因是许可证策略。Oracle收购MySQL之后社区版和企业版的边界越来越清晰一些大企业在合规审查时会对这个比较敏感。PostgreSQL用的是PostgreSQL License一种非常宽松的BSD类许可证没有这个层面的掣肘对很多技术决策者来说是种无形的减压。2. 从功能到性能两份数据库的实打实对比2.1 数据类型和SQL能力差距很大如果你只在MySQL里写简单的CRUD你很难感受到PG的好。但一旦业务复杂起来差异就显现了。我这里列几个我实际用过的点窗口函数PG很早就支持8.4版本就补齐了。MySQL 8.0才开始提供而且某些窗口边界行为还和标准有出入。做数据分析、排名、滑动聚合这些需求PG写起来顺手得多。CTE与递归查询PG的WITH RECURSIVE是完整的递归CTE适合查树形结构、层级菜单、BOM展开。MySQL直到8.0才支持WITH语法递归查询的性能和稳定性也一般。FULL OUTER JOIN这个MySQL至今不支持。两家数据对账、找出两侧差异这个需求很常见在MySQL里你得用UNION模拟写出来又长又难读PG里一行搞定。数组和范围类型PG原生支持MySQL没有。像存标签、存时间段这种需求PG可以不用额外建关联表直接用数组和范围操作搞定。表格对比下来更直观能力项MySQL 8.0PostgreSQL 16/17窗口函数有但支持晚、细节有出入很早支持实现完整递归CTE8.0起支持成熟且性能可靠FULL OUTER JOIN不支持完整支持原生JSONB无JSON类似TEXT有索引友好数组类型无有范围类型无有物化视图无原生支持原生支持表分区8.0起才像样声明式分区成熟生存时间/逻辑复制逻辑复制8.0起内置且成熟2.2 并发控制机制决定了你的体验上限这一块是PG在架构上和MySQL拉开差距的地方。MySQL InnoDB走的是MVCC加行级锁但它的事务隔离级别对读操作的处理相对简化加上历史版本的清理依赖purge线程在高并发下偶尔会有一些奇怪的等待和锁问题。PG用的是更彻底的MVCC设计核心机制是多版本数据行加可见性判断而且PG的读操作不阻塞写写操作不阻塞读。我举个实际例子在MySQL里面跑一个大查询同时有一批小事务在更新数据你可能会看到锁等待飙高SHOW ENGINE INNODB STATUS里面一堆锁记录。同样的场景在PG里查询走的是快照机制更新走的是行版本两边各走各的冲突概率低很多。当然PG的MVCC也有代价——旧版本数据需要清理这个工作在PG里叫VACUUM管理不好会让表膨胀。但PG的优势是这些工具都是内置的auto vacuum默认开启而且调度策略可调Senior DBA调一调就能非常顺滑。2.3 索引能力和查询计划的差距PG在索引这块是真的卷。MySQL主要就是BTree8.0支持倒排索引用于全文检索但和PG一比还是苍白。PG支持B-Tree、Hash、GiST、SP-GiST、GIN、BRIN六种索引类型每种都有明确的适用场景。BRIN对超大表非常有价值——数据量到几亿行的时候B-Tree索引本身可能就有几个GBBRIN索引体积可以缩到原来的千分之一查询走顺序扫描优化的思路完全不同。查询计划的精细度也是PG一个强项。PG的优化器考虑的因素非常多JOIN多种实现方式嵌套循环、哈希连接、归并连接参数化路径分区裁剪并行查询都是成熟可用的。MySQL的优化器这些年也在进步但历史包袱多复杂SQL执行计划跑偏是常有的事我见过不止一次同一套SQL在MySQL里要改写才能用上正确索引在PG里不需要任何调整原样跑就是对的。2.4 高可用和复制方案对比MySQL的主从复制非常成熟这个是其立身之本。但MySQL的复制确实有很多细节要处理半同步复制、GTID、并行复制、级联复制每个都要认真配置一主多从的架构下做failover还得靠MHA或Orchestrator这类外部工具切换流程写脚本写到崩溃是常事。PG的复制设计是我觉得更省心的单主多从基于WAL日志的流复制streaming replication同步/异步模式可配置自动failover有Patroni这种标准组合方案。因为PG的WAL是物理级别的复制延迟监控非常透明pg_stat_replication一看就知道备库落后了多少。而且PG的逻辑复制Logical Replication是内置的可以做到表级订阅做数据汇总、迁移、灰度发布都非常方便。MySQL的逻辑复制到8.0虽然也有了但成熟度和易用性还差一截。3. 从MySQL迁到PostgreSQL我的实操过程和避坑指南3.1 迁移前必须做好的评估清单迁移数据库不是一个单纯的技术替换更像一次业务手术。动手之前你至少要先把下面这些东西理清楚业务功能摸底现存哪些SQL是MySQL特有的语法有没有使用INSERT ... ON DUPLICATE KEY UPDATE这种MySQL方言有没有依赖GROUP_CONCAT、FIND_IN_SET这些函数这些在PG里都有替代但改写是必须的。数据类型映射MySQL的DATETIME对应PG的TIMESTAMPENUM类型PG原生不支持要换成VARCHAR加CHECK约束或者用PG的CREATE TYPE枚举JSON对应JSONBTINYINT(1)对应BOOLEANUNSIGNED INT在PG里没有需要自己用CHECK约束。分库分表改造如果你之前因为MySQL性能上限做了分库分表迁移PG前要认真评估能不能回归单库单表。很多时候分库分表带来的复杂度分布式事务、跨库join、全局ID生成其实可以因为PG更强的性能和功能而直接消解这往往是迁移收益最大的地方。自增主键切换MySQL的AUTO_INCREMENT在PG里是SERIAL或者GENERATED AS IDENTITY。注意PG的序列sequence是独立对象批量导入时序列不会自动同步必须在导入数据后手动setval否则线上直接主键冲突。这些看起来不复杂但每一项在真实业务里都能延展出很多测试用例。我建议迁移前至少留出两到四周做功能回归测试用真实的业务SQL全量跑一遍。3.2 结构迁移和全量数据导入结构迁移我推荐用pgloader这个工具用Lisp写的看起来冷门但极其好用。一条命令就能把MySQL的表结构、数据、索引、约束全部搬过去自动处理类型转换。例如pgloader mysql://user:passlocalhost/app postgresql://user:passlocalhost/app注意pgloader不是万能的复杂约束、视图、存储过程它不一定能转对。我的经验是分两步走先用pgloader处理表结构和基础数据再手工处理视图和函数这些逻辑对象。数据量小怎么导都行但数据量大就要注意方式了。PG导入最快的是COPY命令直接以文件方式灌入比逐条INSERT快一个数量级。pgloader底层就是用COPY实现的所以不用担心它慢。更重要的一个注意点是导入之前先关掉索引再导入导入完再重建索引这个操作能省下大量时间。别信“先建索引再导数据”这种想当然的路子普通B-Tree索引插入是顺序写还好但PG的GIN索引插入开销很大先导数据后建索引能快很多。3.3 增量同步和业务切换全量迁移之后如果业务不能长时间停库就需要做增量同步。我比较推荐用PG原生的逻辑复制来做最后一小段窗口的数据追平先让PG作为MySQL的下游接增量数据追平后再切换读写流量。工具方面Debezium是一个通用CDC方案它对MySQL的binlog解析得很成熟输出到Kafka后再消费写入PG。但这条链路比较重如果只是短时间窗口内同步数据写一个小程序做双写也完全够用。我在实际项目里更常用的一套轻量方案是这样的MySQL业务正常跑用pgloader做全量迁移记下迁移开始的时间点然后启动一个基于binlog的增量同步任务把MySQL产生的增量语句转成PG的SQL回放观察延迟降到很小的量级后把应用配置的连接串指到PG完成切换。整个过程停机时间可以控制在分钟级。3.4 应用层的改造重点这一块经常被低估。你的应用如果用了ORMMyBatis、Hibernate、JPA这些SQL方言的适配通常能省不少事但有些方言细节还是需要改。我碰到过的典型问题MyBatis的selectKey生成主键的写法MySQL是LAST_INSERT_ID()PG是RETURNING idMySQL的LIMIT ?, ?分页写法PG标准是LIMIT ? OFFSET ?但MyBatis方言能自动处理低版本老项目的硬编码SQL就要小心pgJDBC的驱动名是org.postgresql.DriverURL是jdbc:postgresql://host:port/dbname这些配置项在应用配置里别漏了。另外连接池也要注意PG官方推荐的连接池是PgBouncer和Java体系常用的HikariCP、Druid也能共存。我习惯在应用层用HikariCP控制单实例连接数再在前端挂一个PgBouncer做事务级连接池这样能有效减少连接建立开销。如果是小的MySQL连接池直接换JDBC URL绝大多数场景也能直接用。4. 常见问题与排查技巧实录从热词里看大家最常踩的坑4.1 安装部署阶段的典型问题在搜索热词里安装和配置相关的搜索量一直很高比如“postgresql windows 安装 服务启动”、“linux安装postgresql”、“postgresql下载哪个版本”这些。这其实说明PG的安装比MySQL多了一些门槛。PG安装本身不复杂但版本选择和初始化配置这一步很多人会忽略。一个高频问题是Windows安装后服务无法启动。这种情况十有八九是data目录初始化失败常见原因是路径带中文、权限不足或者端口5432被占用。解决方式是手动初始化数据目录initdb -D C:\pgdata -U postgres -E UTF8 --localeen_US.UTF-8 pg_ctl -D C:\pgdata startLinux上另一个高频问题是pg_hba.conf配置不对导致远程连接被拒。PG默认只监听localhost认证用scram-sha-256。如果你按MySQL的习惯只改一下bind-address就想远程连一定会碰壁。要改两处postgresql.conf里把listen_addresses设成*然后在pg_hba.conf里给客户端IP段加一行host all all 0.0.0.0/0 scram-sha-256。注意开完防火墙端口。版本选择这里也给个建议如果能上17就上17PG每个大版本都有明显的性能优化和功能增强新特性落地速度要比MySQL快。如果是老系统迁移考虑稳定16也是没问题的LTS版本。别用太久远的版本PG的优化器很多改进只在最新版本里生效。4.2 数据库连接和SSL问题MySQL连接SSL报错是搜索热词里的常客在PG里也存在对应问题。PG默认其实不要求SSL但如果开启了sslon而客户端驱动版本和服务器不兼容会遇到server certificate does not match hostname这种情况。排查思路很简单先确认连接字符串里是否带ssl参数如果是Java环境检查是否导入了服务器证书到truststore。还有一个我自己常用的偷懒技巧——如果业务不是对加密有强需求开发环境可以临时把ssl设为disable快速定位问题生产再按正式方案配证书。连接池相关的问题也值得说一句。热词里“mysql的数据库连接池”很多人搜换成PG之后连接池的坑主要是默认值差异PG每个连接都是一个独立进程fork模式连接数过高会消耗大量内存这在MySQL里不太明显。所以一旦上PG连接池最大连接数一定要设得比MySQL场景下小得多单实例200个并发连接已经算压力很大了。HikariCP的maximumPoolSize我建议从10起步配合PgBouncer再扛高并发。4.3 数据导入、同步和升级的细节坑“postgresql增量同步软件”也是热词很多人从MySQL迁过来后对同步工具的期待还是MySQL主从复制那种“配好了就不管”的感觉。PG的逻辑订阅虽然内置但使用起来要注意版本匹配发布端和订阅端的PG大版本不能太悬殊跨大版本升级时逻辑订阅会中断。如果是跨版本迁移后的增量同步我建议直接用Debezium或者ETL工具做别硬套PG原生订阅。升级PG还有一个很常见的坑——Linux服务器上原地升级pg_upgrade。这个工具好用但有前置条件新旧版本的程序目录要放在一起数据目录格式要兼容。我遇到过最惨的一次是直接把新包安装了旧数据目录被覆盖然后花了大半天恢复备份。正确顺序是先备份再装新版二进制再用pg_upgrade做link模式升级。link模式非常快因为它不是复制数据而是硬链接文件几分钟就能完成几十GB的库的升级。前提是你做好备份且对新版本有十足信心。4.4 性能排查和运维习惯的变化MySQL运维里有一堆耳熟能详的命令EXPLAIN看执行计划、SHOW PROCESSLIST看当前会话、SHOW ENGINE INNODB STATUS看锁和事务。PG里面对应的命令不一样刚开始用会很不适应查看执行计划EXPLAIN (ANALYZE, BUFFERS) your_query;查看当前会话SELECT * FROM pg_stat_activity;查看锁情况SELECT * FROM pg_locks;或者用pg_blocking_pids(pid)直接找到谁阻塞了谁查看表大小SELECT pg_size_pretty(pg_total_relation_size(table_name));查看索引使用情况SELECT * FROM pg_stat_user_indexes;这几个查询是我日常排查用得最多的。刚转过来的同事经常问我“PG的慢日志在哪”MySQL有slow_query_logPG的默认配置里没有类似开关。实际上PG要在postgresql.conf里设置log_min_duration_statement比如设成1000毫秒所有超过这个阈值的语句都会写到日志里效果和MySQL慢查询日志一样。只是别忘了改完重启或reload。还有一个特别有用的PG功能pg_stat_statements你可以把它理解成“SQL执行统计插件”。它按query维度统计总执行时间、平均执行时间、调用次数、扫描的行数做性能基线分析非常方便。MySQL也有类似工具但PG的这个是官方内置且缺陷更少。我第一次在生产库打开这个插件后半小时就找到了三个需要优化的Top SQL直接省出一台服务器资源。4.5 运维工具箱速查整理一份我常用的PG运维问题速查表问题现象排查命令/思路数据库连不上pg_isready检查端口、pg_hba.conf、防火墙表膨胀严重SELECT relname, n_live_tup, n_dead_tup FROM pg_stat_user_tables;过低时手动VACUUM查询突然变慢EXPLAIN (ANALYZE, BUFFERS)对比执行计划看是否缺统计信息执行ANALYZE主从延迟高SELECT * FROM pg_stat_replication;看write_lag字段数据库磁盘爆满SELECT pg_size_pretty(pg_database_size(dbname));清理WAL归档和旧数据事务卡死/锁冲突SELECT pg_blocking_pids(pid), query FROM pg_stat_activity WHERE stateactive;5. 关于选型、迁移节奏和团队技能转型5.1 什么情况下值得迁什么情况不值得我不想制造话题热度就一味吹PG。说实话不是所有业务都需要从MySQL迁到PG。如果你的业务就是标准CRUD没有复杂查询需求数据量也稳定在千万级以内现有MySQL团队维护得很顺手那迁移带来的收益并不高反而要承担巨大的改动成本和风险。但如果出现以下几种情况我强烈建议认真评估PG第一SQL复杂、报表需求多、希望用标准SQL解决业务问题而不是天天改写SQL方言第二数据量增长快已经在考虑分库分表但心里其实没底第三需要处理JSON/数组/空间/时序这类复杂数据结构又不想引入全套NoSQL第四对数据库许可证和商用合规比较敏感。曾经有一个项目让我印象很深一套会员系统在MySQL上做了16个分库业务联动查询要跨库、跨节点经常出数据一致性问题。迁到PG后单库解决去掉中间件层连接数从600降到80查询响应时间平均降了一半。这不是说PG比MySQL强多少倍而是说PG的单库容量和处理复杂SQL的能力直接消除了分库分表这种反模式。5.2 团队技能转型怎么办数据库切换最容易被低估的是团队技能转型。MySQL DBA和PG DBA虽然都是关系型数据库管理员但日常运维工具、参数体系、调优思维差别很大。我碰到过MySQL玩得很熟的同事刚开始用PG时连psql的元命令都不适应\d和SHOW TABLES完全是两套交互逻辑。我的建议是不要搞突击式迁移而是提前两三个月让开发测试团队先在非核心项目里用PG积累手感。像时间序列、GIS、JSONB这些MySQL边缘能力PG是主力的场景最适合当练手项目。等团队对PG的手感有了再动核心业务整个迁移过程会顺畅很多。另外DBA团队内部可以做一次技术分享轮训把pg_dump、pg_restore、pg_basebackup、pg_upgrade这些核心工具都实际操作一遍做到心里有数。5.3 从整个数据库生态格局看趋势从产业生态的大视角看国内大厂转向PG背后还有一个重要推手多国产数据库产品在做适配时核心参考都是PostgreSQL。像人大金仓、华为openGauss等产品都深度参考甚至兼容PG的体系和生态。这意味着PG的人才培养、生态工具、最佳实践都会持续增长选择PG其实也是在为未来的技术兼容性做铺垫。同时MySQL和PG也不完全是“非此即彼”的关系。很多团队是两者并存的OLTP交易型业务继续跑在MySQL上分析型、复杂查询型、地理位置型等场景交给PG。作为从业者我更愿意把两者都吃透而不是成天争论谁更好。数据库技术没有一劳永逸的银弹但PostgreSQL确实在越来越多场景下展现出了更强的可扩展性和标准完备性这是不争的事实。我个人在实际操作中的体会是工具选择永远服务于业务复杂度。如果你的业务已经到了MySQL需要靠各种中间件和运维黑魔法硬撑的地步那PG大概率能把你从这套复杂度里解放出来。迁移确实有成本和风险但长期收益通常远超你的预期。最后再分享一个小技巧无论最后选型如何先把两套库都装起来拿一条真实的复杂业务SQL分别跑一遍看看执行计划差异你大概率会对PG的优化器产生新的认识。
返回列表