MySQL还是PostgreSQL?亿级支付系统真实选型与迁移复盘你还在用“大家都在用”作为数据库选型的主要理由吗?当核心表逼近 3 亿行、交易流水接近 9 亿行,当 JSON 查询、复杂对账、风控聚合和区域合规同时压向数据库时,真正危险的并不是 MySQL 或 PostgreSQL 本身,而是团队仍在用创业早期的简单 CRUD 思维,支撑已经进入复杂交易阶段的业务。本文通过一个脱敏后的跨境支付 SaaS 案例,复盘一次从 MySQL 5.7 事故止血、数据库重新选型、PostgreSQL 架构重构,到生产迁移和稳定性治理的完整过程。案例说明:文中的业务规模、事故时间和部分技术细节经过脱敏与合并处理,用于还原典型问题,不对应某一家公开公司的完整生产环境。标题中的“删库跑路”和“稳如狗”是技术团队对事故严重性与稳定性目标的戏称,不代表真实删除数据库,也不构成对 PostgreSQL 的绝对承诺。文中性能收益只代表该案例的压测与落地结果,不能替代你自己的基准测试。一、凌晨 2:17,支付系统开始雪崩凌晨 2:17,PagerDuty 连续告警:支付创建接口 P99 延迟从 180 ms 上升到 12 s应用连接池等待线程持续增长MySQL 主库 CPU 长时间维持在 100%订单创建、支付确认和商户查询同时超时RocketMQ 消费开始积压,重试流量进一步放大数据库压力这不是一次简单的慢 SQL,而是典型的数据库故障放大链:错误执行计划 ↓ 大量扫描与 CPU 消耗 ↓ SQL 执行时间增长 ↓ 数据库连接迟迟不归还 ↓ HikariCP 连接池耗尽 ↓ 业务线程排队与超时重试 ↓ 流量被二次放大 ↓ 订单、支付、回调服务一起雪崩当时的平台是一套高速增长的跨境支付 SaaS:指标事故前规模日均订单量约 800 万笔订单创建峰值约 5,200 QPS注册商户超过 12 万家核心订单表约 2.8 亿行交易流水表接近 9 亿行技术架构Spring Cloud + MySQL 5.7 + Redis + RocketMQ数据库拓扑一主三从,半同步复制事故集中在一张订单扩展信息表上。为了兼容不同国家、渠道和商户的自定义字段,早期设计把大量扩展属性放进了多个 JSON 列。某次索引改造后,一个包含JSON_EXTRACT、类型转换和普通列过滤的查询出现严重基数估算偏差,优化器选择了代价极高的访问路径。团队尝试了所有熟悉的止血手段:回滚索引变更临时限制问题商户流量使用FORCE INDEX拆分查询扩容只读实例重启连接异常的应用 Pod事故最终被压住,但所有人都清楚:这不是一次孤立的索引事故,而是数据模型、查询复杂度、数据库版本和业务规模共同失配的结果。事故复盘会上,CTO 提出了一个比“怎么优化这条 SQL”更重要的问题:我们的核心数据库,是否还适合未来三年的业务?候选方案只剩两个:升级到 MySQL 8.4 LTS,重构 JSON 和索引设计迁移到 PostgreSQL,重建交易数据模型与数据库治理体系二、真正的问题,不是 MySQL 慢,而是选型逻辑失效了必须先澄清一个容易传播、却不准确的结论:MySQL 并不是“低级数据库”,PostgreSQL 也不是所有场景的标准答案。MySQL 8.4 LTS 依然是成熟、稳定、生态完善的 OLTP 数据库。对于数据模型稳定、访问模式清晰、团队 MySQL 经验深厚的业务,它完全可以支撑非常大的规模。问题在于,我们当年的选型理由只有一句:大家都用 MySQL,招人容易,先上再说。这个理由在创业初期没有错,但它存在有效期。数据库选型至少要回答下面六个问题:业务主要是简单主键访问,还是复杂关系查询?半结构化数据是偶尔存储,还是核心查询对象?写入是追加为主,还是高频更新为主?是否需要窗口函数、复杂聚合、地理空间或全文检索?团队能否建立统计信息、执行计划、锁和存储膨胀治理能力?未来扩展更依赖分库分表,还是单库能力与插件生态?事故发生时,我们的答案已经与创业初期完全不同:对账需要多表关联、窗口函数和大范围聚合风控规则需要组合条件、表达式与 JSON 路径查询商户扩展字段已经成为产品能力,而不是日志附件支付状态更新频繁,长事务与锁竞争风险显著增加地理位置、国家限制和区域合规逐渐进入核心链路数据库不再只是“存取数据”,而是交易规则的一部分因此,我们要解决的不是“哪条 SQL 更快”,而是:哪种数据库能力模型,更符合未来的业务复杂度。三、先纠正四个常见误区很多 PostgreSQL 与 MySQL 对比文章,为了制造冲突,会把技术差异写成错误结论。真正的生产选型必须先去掉这些噪声。误区一:MySQL JSON 只是字符串语法糖这不准确。MySQL 的JSON是原生数据类型,内部采用二进制格式,并支持 JSON 函数、局部更新、生成列索引和多值索引。它的问题不是“本质上只是字符串”,而是:JSON 列通常不能像普通标量列那样直接建立通用索引常见做法是抽取路径到生成列,再为生成列建立 B-Tree 索引数组场景可以使用多值索引,但可索引表达式与适用查询存在边界当查询路径频繁变化时,索引设计成本会快速上升PostgreSQLjsonb的优势在于其通用性更强:可直接使用 GIN 对 JSON 文档中的键和值建立倒排索引支持包含、存在性和jsonpath查询可以为稳定的热点路径建立表达式索引能在同一数据模型中组合 B-Tree、GIN、部分索引和表达式索引所以,更准确的结论是:MySQL JSON 适合路径相对稳定、可提前抽取索引字段的场景;PostgreSQL JSONB 更适合查询维度多、路径变化快、半结构化数据参与核心检索的场景。误区二:MySQL 没走索引就会把行锁升级成表锁这同样不准确。InnoDB 没有常规数据库中那种“锁数量达到阈值后自动升级为表锁”的机制。问题在于,InnoDB 的记录锁与索引访问路径密切相关:使用索引范围扫描时,会锁定扫描到的索引记录或范围在可重复读隔离级别下,部分查询还可能涉及 gap lock 或 next-key lock如果没有可用索引,更新或删除可能扫描并锁住大量记录从业务表现看,它可能接近“整表都被阻塞”,但技术上不是锁升级PostgreSQL 同样会发生死锁,也不是“天然没有锁问题”。它的特点是行版本与行级锁语义不同,不会因为扫描范围大就把行锁升级为表锁,但长事务、热点行更新和锁顺序不一致仍然会导致严重阻塞。因此真正的工程结论应该是:两者都需要正确索引、短事务、统一锁顺序和死锁重试。数据库不同,只是故障形态不同。误区三:PostgreSQL 开启 JIT 后,普通 JSON 查询都会飞起来JIT 并不是 OLTP 万能加速器。PostgreSQL 的 JIT 需要付出编译成本,通常只有执行代价足够高、表达式计算足够重的查询才可能受益。对于支付创建、主键查询、短事务和毫秒级 SQL,JIT 的编译开销往往得不偿失。JIT 更适合:长时间运行的复杂聚合大量表达式计算分析型查询CPU 计算成本明显高于编译成本的执行计划我们的生产策略不是“打开 JIT 就变快”,而是:OLTP 主库按压测结果决定是否关闭或提高触发阈值对账与分析库单独评估 JIT永远通过EXPLAIN (ANALYZE, BUFFERS, WAL)验证,而不是凭感觉配置误区四:PostgreSQL 逻辑复制可以直接实现支付系统双向多活这是全文最危险的误区。PostgreSQL 原生逻辑复制可以进行表级数据复制,但它不是自动冲突解决引擎。订阅端出现唯一键冲突时,复制可能停止;同一行被多个来源修改时,也需要明确治理策略。对于支付订单、账户余额和复式记账流水,简单的“最后写入胜利”不能保证金融正确性:两个区域可能同时接受同一幂等键时钟偏差会破坏 LWW 判断余额和流水不能通过覆盖旧值解决冲突一个区域已完成退款,另一个区域仍可能继续扣款正确方向不是“所有区域都能随便写同一条数据”,而是:按商户、账户或账本确定唯一写入归属同一业务实体只允许一个权威写入区域跨区域采用异步复制和容灾切换切换必须伴随 fencing、流量收敛和幂等校验财务账本以不可变流水为准,不使用 LWW 覆盖四、为什么最终选择 PostgreSQL经过两个月的 PoC,我们没有用“跑一个 sysbench”决定数据库,而是复制真实访问模型,针对六类关键负载进行评测:支付订单创建与幂等冲突支付状态高频更新商户扩展字段 JSON 查询多表对账与窗口函数账本流水范围扫描与聚合大表维护、索引创建和故障恢复1. 查询优化器:不是谁永不选错,而是谁更容易治理MySQL 和 PostgreSQL 都是基于成本的优化器,也都会因为统计信息不准、数据倾斜和参数化查询而选错计划。PostgreSQL 对我们更有价值的地方在于:支持多列扩展统计信息,可描述列之间的函数依赖、联合不同值数量和常见值组合执行计划节点丰富,复杂关联与聚合的选择空间更大支持表达式统计信息和表达式索引EXPLAIN ANALYZE、缓冲区统计和运行时监控组合更完整可以按会话调整代价参数,但不建议把参数调优当作日常救火方式例如,支付订单中的merchant_id、country_code和channel_code往往高度相关。如果优化器把它们当作完全独立条件,可能严重低估或高估结果行数。PostgreSQL 可以显式创建扩展统计信息:CREATESTATISTICSst_payment_route(dependencies,mcv)ONmerchant_id,country_code,channel_codeFROMpayment_orders;ANALYZEpayment_orders;这并不意味着执行计划永远正确,但它给了团队更细的治理手段。2. JSONB:灵活字段可以查,但不能滥用我们最终采用了“关系字段 + JSONB 扩展字段”的混合模型。强一致、频繁过滤和参与资金计算的字段必须结构化:订单号商户号幂等键支付状态金额与币种支付渠道创建时间版本号变化快、低频访问或不同渠道差异明显的字段放入 JSONB:渠道原始响应风控解释信息商户自定义字段设备与客户端扩展信息非核心展示属性我们明确禁止下面这种设计:{"merchantId":"M10001","amount":"99.00","currency":"USD","status":"SUCCESS","createdAt":"2026-07-28T10:00:00Z"}如果金额、状态、时间和商户号全部塞进 JSONB,数据库仍然会变成不可维护的文档仓库。JSONB 的正确定位是:为关系模型提供受控的扩展能力,而不是逃避数据建模。3. 并发控制:更可预测,但不是零成本PostgreSQL 更新一行时会产生新的行版本,旧版本由 VACUUM 体系回收。它带来了两个明显特点:读写之间通常不会互相阻塞高频更新表必须认真治理死元组、长事务和表膨胀支付系统最怕的不是“数据库有维护机制”,而是维护行为不可观测。PostgreSQL 的优势是问题通常可以从这些视图中被直接定位:pg_stat_activitypg_lockspg_stat_user_tablespg_stat_statementspg_stat_progress_vacuumpg_stat_progress_create_index代价也很明确:团队必须理解 VACUUM、事务 ID、冻结、可见性映射和长事务影响。4. SQL 表达力:减少应用层的数据搬运我们的对账、结算和风控查询大量使用:窗口函数FILTERLATERAL公共表表达式DISTINCT ON数组与范围类型INSERT ... ON CONFLICTFOR UPDATE SKIP LOCKEDPostgreSQL 让一部分原本散落在 Java 内存中的聚合逻辑回到了数据所在的位置。但我们也给“计算下推”划了一条边界:适合下推:集合过滤、聚合、排序、窗口计算、批量更新不适合下推:外部 HTTP 调用、跨服务编排、复杂业务状态机数据库函数只承载稳定的数据规则,不承载整个支付业务5. 扩展能力:解决明确问题,而不是安装插件上瘾PostgreSQL 的扩展生态确实强大,例如:PostGIS:地理空间数据pg_stat_statements:SQL 统计pg_trgm:模糊匹配FDW:外部数据访问Citus:特定分布式场景但插件越多,升级、兼容、备份和故障恢复成本越高。我们的原则是:核心交易库只安装有明确业务价值、具备升级路径、通过恢复演练的扩展。五、PostgreSQL 与 MySQL:一次更公平的正面比较截至本文更新时,PostgreSQL 18 是当前稳定大版本,PostgreSQL 社区对每个大版本提供五年支持;MySQL 8.4 属于 LTS 轨道。实际生产应选择团队、云厂商或商业支持明确覆盖的版本,而不是盲目追新。维度PostgreSQLMySQL 8.4 LTS选型判断简单 OLTP 与主键访问成熟,性能稳定成熟,生态和经验更广都适合,重点看团队与云服务复杂 SQL多种 Join、窗口函数、扩展统计和丰富执行节点已持续增强,但复杂访问模型仍需充分验证复杂对账和分析型 OLTP 更偏向 PGJSONJSONB、GIN、表达式索引、jsonpath原生 JSON、生成列索引、多值索引路径变化大时 PG 更灵活并发与锁MVCC、行级锁、不做行锁升级,需治理膨胀InnoDB MVCC、记录锁、gap/next-key lock都会死锁,故障形态不同分区声明式分区,能力完整原生分区成熟都可用,关键是分区键设计高可用流复制、逻辑复制、成熟 Operator 与 HA 生态Group Replication、InnoDB Cluster、云服务成熟都能生产落地运维人才人才相对少,能力要求更偏数据库原理市场人才多,经验丰富MySQL 招聘与交接通常更容易扩展生态类型、索引、FDW、PostGIS 等能力突出更强调核心数据库与官方产品生态有高级数据能力需求时 PG 优势明显升级与兼容大版本升级需规划,可逻辑迁移或工具升级LTS 路线明确,生态工具成熟都不能把升级当作临时操作分布式扩展Citus 等方案,适合特定模型分库分表生态成熟取决于分片键和访问模型我们最后为什么没有选择升级 MySQL 8.4不是因为 MySQL 8.4 不够好,而是我们的关键需求组合更偏向 PostgreSQL:JSON 字段路径和过滤条件变化快多表对账与窗口查询持续增加希望使用统一数据库承载关系、JSON 和地理空间能力团队愿意建立 PostgreSQL 运维与内核机制认知新架构本来就需要重新设计数据模型,不是简单原地升级如果你的系统主要是稳定表结构、主键查询、简单事务,并且已经拥有成熟 MySQL 运维体系,继续使用 MySQL 8.4 往往比跨数据库迁移更理性。六、重构后的支付数据架构数据库替换不是把 JDBC URL 从 MySQL 改成 PostgreSQL。真正的重构包括:拆分订单事实、支付尝试、账本和扩展信息引入事务 Outbox,消除数据库提交与 MQ 发送之间的双写窗口将分析负载从物理只读备库进一步隔离到逻辑订阅库统一连接池、SQL 超时、事务超时和慢查询治理把恢复演练纳入发布流程1. 总体架构业务服务层Physical Streaming ReplicationPhysical Streaming ReplicationLogical Replication