MySQL vs PostgreSQL 对比
本文档系统对比 MySQL(InnoDB) 与 PostgreSQL 的核心差异,涵盖事务、索引、复制、类型系统、运维调优和迁移等关键主题,适合开发、运维和架构师参考。
目录
- 概述与选型建议
- [事务、隔离级别与 MVCC](#事务、隔离级别与 MVCC)
- 索引、执行计划与查询优化
- 复制、备份与高可用
- 数据类型、JSON、全文检索与扩展
- 运维、性能调优与常见坑
- 迁移与兼容性对照
1. 概述与选型建议
1.1 两个数据库的定位
MySQL(常见为 InnoDB)
- 更常见于 Web OLTP:简单模型、高 QPS、读扩展(主从/读写分离)路径成熟
- 典型经验依赖:联合索引/覆盖索引、合理的主键设计、避免锁冲突
- 生态成熟:主从复制、读写分离、中间件支持完善
PostgreSQL
- 更像"关系型数据库平台":SQL 表达力强、类型系统强、扩展生态丰富
- 典型强项:复杂查询、
jsonb+ GIN、GIS、向量检索、逻辑复制 - 标准更强:更接近 SQL 标准,一致性语义更清晰
1.2 选型先对齐三个指标
- 一致性目标:是否允许读到旧数据?是否允许丢数据?
- 可用性目标:RPO/RTO、故障切换是否自动、是否要跨机房
- 成本目标:团队熟悉度、监控与排障能力、变更窗口与演练成本
1.3 一句话记忆
- MySQL:更像"高性价比 OLTP 引擎 + 成熟的读扩展套路"
- PostgreSQL:更像"标准更强、功能更全、扩展更强的通用数据库平台"
2. 事务、隔离级别与 MVCC
2.1 核心概念速览
- 事务(Transaction):一组操作要么全部成功、要么全部失败回滚
- ACID:原子性、一致性、隔离性、持久性
- 隔离级别:决定并发事务之间"可见到什么程度的数据"
- MVCC:通过"多版本数据"实现并发读写,减少读写互斥
2.2 隔离级别对照(实践中最关键)
不同数据库对同名隔离级别的具体实现细节可能不同,尤其是 MySQL(InnoDB) 的
REPEATABLE READ与锁策略。
READ COMMITTED(读已提交)
- PostgreSQL:常用默认隔离级别。每条语句看到的是执行瞬间已提交版本
- MySQL:也支持。对减少锁冲突有帮助,但一致性语义与 gap/next-key 锁等仍有关联
REPEATABLE READ(可重复读)
- PostgreSQL:基于快照,事务内多次读取同一行可保持一致
- MySQL(InnoDB):默认隔离级别。除了快照一致性,还会结合 next-key locking 等机制来处理范围查询与"幻读"风险
SERIALIZABLE(可串行化)
- PostgreSQL:基于 SSI(Serializable Snapshot Isolation),工程上可用
- MySQL:可串行化会更保守,可能导致更多锁与阻塞
2.3 两者 MVCC 的实现差异(理解性能与膨胀的关键)
PostgreSQL:行版本 + VACUUM 回收
- 更新/删除并不"原地覆盖",而是产生新版本(旧版本留在表里)
- 旧版本回收依赖 VACUUM / autovacuum
- 关键影响:
- 表膨胀(bloat):写多/更新多但 vacuum 不及时会导致空间膨胀与性能下降
- 长事务:会阻止旧版本回收,导致膨胀更严重
MySQL(InnoDB):undo log 维持历史版本
- 通过 undo log 维护一致性读所需的旧版本
- purge 线程清理不再需要的 undo 记录
- 关键影响:
- 长事务/大事务会导致 undo 累积,影响性能与空间
- 一致性读与当前读(加锁读)语义区分明显
2.4 当前读 vs 一致性读(非常重要)
PostgreSQL
- 普通
SELECT基于快照读 SELECT ... FOR UPDATE/SHARE会加锁(行级锁),用于保护并发更新
MySQL(InnoDB)
- 普通
SELECT常为一致性读(基于 MVCC) SELECT ... FOR UPDATE/LOCK IN SHARE MODE等属于"当前读",更容易触发锁冲突
2.5 幻读、范围查询与 next-key 锁
- PostgreSQL:依赖 MVCC + SSI/锁机制来保证隔离语义
- MySQL(InnoDB) :在
REPEATABLE READ下,范围条件更新/加锁读可能触发 next-key lock(记录锁+间隙锁),用于抑制幻读,但也更容易造成锁等待与死锁
2.6 工程实践建议(落地)
- 尽量避免长事务 :
- PostgreSQL:长事务会阻止 vacuum 回收旧版本
- MySQL:长事务会拖慢 purge,导致 undo 压力
- 批量写入/更新要分批:减少锁持有时间与版本积累
- 线上排查锁问题 :
- PostgreSQL:关注
pg_stat_activity,pg_locks,pg_stat_statements - MySQL:关注
INNODB_LOCKS,INNODB_LOCK_WAITS或 performance_schema,SHOW ENGINE INNODB STATUS
- PostgreSQL:关注
2.7 速记清单 / 对比表
| 维度 | MySQL(InnoDB) | PostgreSQL |
|---|---|---|
| 默认隔离级别 | REPEATABLE READ | READ COMMITTED |
| 幻读抑制 | next-key lock (范围锁) | MVCC + SSI (RC) / 强串行化 (SERIALIZABLE) |
| 长事务影响 | Undo 累积, Purge 压力 | 阻塞 Vacuum, 表膨胀(Bloat) |
| 当前读 | SELECT ... FOR UPDATE 锁定并回表 |
FOR UPDATE/SHARE 行锁 |
| 一致性读 | MVCC 快照 (undo) | MVCC 快照 (行版本) |
| Serializable | 粗暴加锁, 性能开销大 | SSI 算法, 工程可用 |
面试速背:"MySQL RR + next-key, PG RC 默认 + SSI 串行化"。
2.8 SQL 对照示例
可重复读 vs 读已提交
sql
-- MySQL 切到 READ COMMITTED 级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM orders WHERE id = 1;
-- ... 同时另一个会话更新该行并提交 ...
SELECT * FROM orders WHERE id = 1; -- 看到新值(MySQL RC)
COMMIT;
sql
-- PostgreSQL 默认就是 READ COMMITTED
BEGIN; -- 默认 RC
SELECT * FROM orders WHERE id = 1;
-- 并发会话更新并提交后
SELECT * FROM orders WHERE id = 1; -- 看到新值
COMMIT;
next-key lock 触发示例(MySQL)
sql
-- 会话 A
START TRANSACTION;
SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE; -- 范围锁(记录+间隙)
-- 会话 B
UPDATE users SET age = 25 WHERE age = 19; -- 阻塞,因为 19 在间隙内
3. 索引、执行计划与查询优化
3.1 索引类型对照
MySQL(InnoDB) 简要
- 主索引:聚簇索引(B+Tree),行数据存于主键叶节点
- 二级索引:B+Tree,叶子存 (索引列, 主键),回表查行
- 强调联合索引最左前缀、覆盖索引减少回表
PostgreSQL 四种常用索引访问方法
PostgreSQL 支持多种索引访问方法(Index Access Methods),其中最常用的是 B-Tree ,而 GIN、GiST、BRIN 则针对特定数据类型或查询模式进行了优化。
1)B-Tree(平衡树)索引
-
默认索引类型
执行
CREATE INDEX idx ON table (col);时,默认创建的就是 B-Tree 索引 -
原理
- 基于平衡多路搜索树,所有叶子节点处于同一层
- 支持高效查找、范围扫描、排序
-
适用场景
- 等值查询:
WHERE col = 'value' - 范围查询:
WHERE col BETWEEN 10 AND 100、col > 50 - 排序:
ORDER BY col - 前缀匹配(仅限左前缀):
WHERE col LIKE 'abc%'(若为文本)
- 等值查询:
-
不适用
- 右模糊或全模糊:
LIKE '%abc'、LIKE '%abc%' - 数组/JSON/全文搜索等复杂类型(虽可建但效率低)
- 右模糊或全模糊:
2)GIN(Generalized Inverted Index,广义倒排索引)
-
原理
- 为「一个值对应多个键」的场景设计(如数组、JSON、全文检索)
- 结构:键 → 行 ID 列表(Posting List)
- 适合「包含」类查询(如「文档是否包含词 X」)
-
适用场景
数据类型 常用操作符 示例 jsonb @>,?, `?,?&`array &&,@>,<@ids && ARRAY[1,2,3]全文检索 @@等tsv @@ to_tsquery('word') -
不适用
- 简单等值/范围、排序(用 B-Tree 更合适)
- 写多读少且对写入延迟敏感(GIN 写成本较高)
3)GiST(Generalized Search Tree)
-
原理
- 通用搜索树,可承载多种「近似/范围」语义
- 写入通常比 GIN 轻,查询可能需更多回表或二次过滤
-
适用场景
- 地理空间:点、线、面、范围(常与 PostGIS 配合)
- 全文检索:写多读少时,可作为 GIN 的替代(索引更小、写更快)
- 范围类型(range)、网络类型(inet/cidr)等
4)BRIN(Block Range INdex)
-
原理
- 按连续物理块存储「最小/最大」等摘要,不存每个键
- 适合物理存储与键值强相关(如按时间顺序写入的时间列)
-
适用场景
- 超大表 + 列在物理上有序(如按时间递增插入的
created_at) - 范围查询可「跳过」大量数据块,显著减少 I/O
- 超大表 + 列在物理上有序(如按时间递增插入的
-
不适用
- 数据无序(如 UUID 主键)
- 高频点查(
WHERE id = 123) - 小表(索引收益 < 维护成本)
3.2 四种索引对比总结
| 索引类型 | 最佳场景 | 写入性能 | 查询性能 | 内存/磁盘占用 | 是否支持排序 |
|---|---|---|---|---|---|
| B-Tree | 等值、范围、排序 | 中 | 极快 | 中~高 | ✅ |
| GIN | JSON/数组/全文(包含查询) | 慢 | 快(过滤) | 高 | ❌ |
| GiST | 空间、全文(写多) | 快 | 中(常需回表) | 中 | ❌ |
| BRIN | 超大表 + 物理有序列 | 极快 | 快(范围跳过) | 极低 | ❌ |
3.3 如何选择?
- 普通字段 (int、text、timestamp)→ B-Tree
- JSONB / 数组 / 全文检索 (读多)→ GIN
- 地理空间 / 全文检索 (写多)→ GiST
- TB 级日志表、按时间查询 → BRIN
3.4 EXPLAIN 怎么看(排查思路)
PostgreSQL
- 使用
EXPLAIN (ANALYZE, BUFFERS)查看实际执行计划和 I/O - 关注:
Actual Rows、Loops、Buffers、Index Only Scan
MySQL
- 使用
EXPLAIN或EXPLAIN ANALYZE查看执行计划 - 关注:
type(ALL最差)、rows、Extra(Using index表示覆盖索引)
3.5 SQL 对照示例
覆盖索引 / Index Only Scan
MySQL:
sql
-- 二级索引包含所有查询列,避免回表
CREATE INDEX idx_email_status ON users(email, status);
EXPLAIN FORMAT=JSON
SELECT email, status FROM users WHERE email='a@b.com';
PostgreSQL:
sql
-- Index Only Scan 需要可见性图 (Visibility Map)
CREATE INDEX idx_email_status ON users(email, status);
EXPLAIN (ANALYZE, BUFFERS)
SELECT email, status FROM users WHERE email='a@b.com';
表达式 / 生成列索引
MySQL:
sql
ALTER TABLE users ADD COLUMN email_lower VARCHAR(255) GENERATED ALWAYS AS (LOWER(email)) STORED;
CREATE INDEX idx_email_lower ON users(email_lower);
SELECT * FROM users WHERE email_lower='a@b.com';
PostgreSQL:
sql
CREATE INDEX idx_email_lower ON users(LOWER(email));
SELECT * FROM users WHERE LOWER(email)='a@b.com';
大分页优化 (Keyset Pagination)
MySQL / PostgreSQL 通用:
sql
SELECT * FROM users WHERE id < 100000 ORDER BY id DESC LIMIT 20;
4. 复制、备份与高可用
4.1 复制模型(Replication)
MySQL
- 主从复制(基于 binlog)
- 异步复制:吞吐高,但主故障可能丢最后一段数据(RPO>0)
- 半同步复制:更接近强一致,但写延迟增加;仍需明确 ACK 条件
- 常见拓扑
- 一主多从(读扩展常见)
- 级联复制(减轻主库压力,但链路更复杂)
PostgreSQL
- 物理复制(流复制,基于 WAL)
- Standby 重放 WAL 与主库保持一致
- 适合"整库复制 + 快速切换"
- 逻辑复制(按表发布/订阅)
- 适合:在线迁移、按表拆分、选择性同步
- 注意:逻辑复制不是"强一致的实时双活",要理解延迟与冲突边界
4.2 同步 vs 异步:一致性与可用性权衡
-
异步复制
- 优点:写延迟低、吞吐高、对网络抖动不敏感
- 缺点:主故障可能丢数据(尤其是最近提交)
-
同步复制
- 优点:更强一致性,RPO 更接近 0
- 缺点:写延迟显著增加;链路抖动会放大为写抖动;可用性依赖多数派/确认策略
学习关键:同步复制是"一致性换延迟与可用性"的典型交易。
4.3 高可用(HA)常见形态
MySQL 常见 HA 形态
-
主从 + VIP/代理层
- 依赖外部组件做故障检测与切换
- 需要面对:复制延迟、脑裂防护、读写路由
-
组复制 / MGR(概念层)
- 追求更强一致性与自动选主能力
- 需要理解:成员一致性、网络分区、仲裁
PostgreSQL 常见 HA 形态
- Primary + Standby(流复制) + 自动故障转移
- 关键:选主与仲裁(避免脑裂)
- 关键:故障切换后数据一致性评估(特别是异步复制)
4.4 读扩展与延迟(业务一致性视角)
-
MySQL:读写分离非常普遍,但要默认"从库可能滞后"
- 业务需要明确:哪些读必须走主库、哪些可以走从库
-
PostgreSQL:读扩展同样可用(热备只读),但也要处理延迟与一致性
无论哪家数据库:复制延迟是常态,不是异常。
4.5 备份与恢复(Backup/Restore)
MySQL
- 逻辑备份:mysqldump(小中规模、可移植性好,速度较慢)
- 物理备份:适合大库(取决于你们体系工具)
- PITR(时间点恢复):核心思想是"全量备份 + binlog 重放到目标时间点"
PostgreSQL
- 逻辑备份:pg_dump/pg_restore(常用)
- 物理备份:base backup + WAL 归档
- PITR:核心思想是"base backup + WAL 归档回放到目标时间点"
4.6 SQL / 命令示例
PostgreSQL:逻辑复制(概念示例)
Publisher(源库):
sql
CREATE PUBLICATION pub_users FOR TABLE users;
Subscriber(目标库):
sql
CREATE SUBSCRIPTION sub_users
CONNECTION 'host=... port=... user=... password=... dbname=...'
PUBLICATION pub_users;
5. 数据类型、JSON、全文检索与扩展
5.1 类型系统:PostgreSQL 通常更"强类型/更丰富"
PostgreSQL 类型优势来自哪里
- 类型 + 操作符 + 索引访问方法是"强绑定"的:同一个数据类型往往配套丰富的操作符与索引策略
- 允许你在业务里表达更真实的语义(例如网络地址、范围、数组、JSON 文档),并能用索引支撑查询
PostgreSQL 常见优势类型/能力
uuidjsonbarrayrange(范围类型)inet/cidr/macaddrtimestamptz(带时区语义的时间戳)- domain / enum / 自定义类型(更强的约束表达能力)
MySQL 的常见类型特点
- 类型覆盖也很广(整数/浮点/DECIMAL/日期时间/JSON 等)
- 但在"类型系统严谨性"和"复杂类型的索引/操作符生态"上通常弱于 PostgreSQL
- 工程上经常通过:
- 生成列(generated column)
- 冗余列(反范式)
- 额外索引表/ES
来补齐某些复杂查询能力
5.2 JSON:两者都能做,但 PostgreSQL 的 jsonb 生态更强
PostgreSQL:json vs jsonb
json:存文本,保留原始格式;通常不建议在查询密集型场景使用jsonb:二进制结构化存储,支持更强的索引与操作符,是多数业务的首选
PostgreSQL:jsonb 的查询与索引
->/->>:取 JSON 字段(->>取文本)@>:包含(containment)查询- GIN :对
jsonb的包含/键存在等查询非常有效
MySQL:JSON 查询与索引落地
- MySQL 有
JSON类型与JSON_EXTRACT/->等能力 - 索引 JSON 的常见工程套路:
- 用 生成列把 JSON key 提取到普通列
- 再对生成列建普通 B-Tree 索引
学习要点:MySQL JSON 能用,但"复杂检索 + 灵活索引"的体验通常不如 PostgreSQL
jsonb+ GIN。
5.3 全文检索(FTS)
PostgreSQL
- 内建全文检索能力:
tsvector/tsquery - 常与 GIN 搭配建索引
- 对中小规模全文检索很实用(减少系统复杂度)
MySQL
- InnoDB 支持 FULLTEXT
- 更适合简单检索/站内搜索类需求
更大规模/更复杂检索仍常走专用搜索系统,但数据库内建 FTS 对"快速落地"很有价值。
5.4 扩展生态(PostgreSQL 的核心竞争力之一)
PostgreSQL 常见扩展
- PostGIS:GIS
- TimescaleDB:时间序列
- pgvector:向量检索
- FDW:外部数据封装器(把外部数据源"像表一样查询")
MySQL 扩展方式
- UDF / 存储过程等
- 整体生态与"平台化扩展能力"通常弱于 PostgreSQL
5.5 SQL 对照示例
JSON 字段查询与索引
PostgreSQL:
sql
-- 假设有表 events(id bigint, payload jsonb)
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
payload jsonb NOT NULL
);
-- GIN 索引(适合 @> 包含查询等)
CREATE INDEX idx_events_payload_gin ON events USING gin (payload);
-- 查询:payload 包含 {"type":"pay"}
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM events WHERE payload @> '{"type":"pay"}';
-- 取字段文本
SELECT payload->>'type' FROM events WHERE id = 1;
MySQL:
sql
-- 假设有表 events(id bigint primary key auto_increment, payload json)
CREATE TABLE events (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
payload JSON NOT NULL,
-- 生成列抽取 JSON key
type VARCHAR(32) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(payload, '$.type'))) STORED,
INDEX idx_events_type(type)
);
EXPLAIN SELECT * FROM events WHERE type = 'pay';
全文检索
PostgreSQL:
sql
-- 简化示例:title + body 做全文
CREATE TABLE docs (
id BIGSERIAL PRIMARY KEY,
title text,
body text,
tsv tsvector
);
-- 维护 tsv(实际生产通常用触发器或生成列策略)
UPDATE docs SET tsv = to_tsvector('simple', coalesce(title,'') || ' ' || coalesce(body,''));
CREATE INDEX idx_docs_tsv_gin ON docs USING gin(tsv);
SELECT * FROM docs WHERE tsv @@ plainto_tsquery('simple', 'postgres');
MySQL:
sql
CREATE TABLE docs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title text,
body text,
FULLTEXT KEY ft_title_body(title, body)
);
SELECT * FROM docs WHERE MATCH(title, body) AGAINST('postgres' IN NATURAL LANGUAGE MODE);
6. 运维、性能调优与常见坑
6.1 运维心智模型差异
PostgreSQL
需要重点理解:
- VACUUM / autovacuum
- 表膨胀(bloat)
- 统计信息(ANALYZE)
- Checkpoint/WAL 行为
很多"性能问题"根因是:长事务 + vacuum 跟不上 + 膨胀 + 统计不准。
MySQL(InnoDB)
需要重点理解:
- Buffer Pool
- redo/undo
- purge
- 主从复制延迟
- 死锁与锁等待诊断
6.2 常见性能指标与排查入口
PostgreSQL
- 慢 SQL:建议启用
pg_stat_statements(若允许) - 锁与阻塞:
pg_stat_activity、pg_locks - 执行计划:
EXPLAIN (ANALYZE, BUFFERS)
MySQL
- 慢 SQL:slow query log / performance_schema
- 锁与死锁:
SHOW ENGINE INNODB STATUS,以及 performance_schema 相关表 - 执行计划:
EXPLAIN/EXPLAIN ANALYZE
6.3 PostgreSQL 特有的高频坑
长事务导致膨胀
- 症状:磁盘涨、索引变大、查询变慢
- 方向:定位长事务来源;确保 autovacuum 参数合理
autovacuum 不够积极
- 写入频繁的表可能需要更激进的阈值与成本参数
work_mem 设置不当
- 过小:排序/Hash 聚合落盘
- 过大:并发下内存爆
6.4 MySQL 特有的高频坑
不合理的联合索引
- 常见导致大量回表与随机 I/O
REPEATABLE READ + 范围更新引发大量 next-key lock
- 症状:锁等待多、吞吐下降
- 方向:调整隔离级别/改写语句/拆分批量
主从延迟被忽视
- 读写分离后读取到旧数据,引发业务一致性问题
6.5 通用建议(两者都适用)
- 让事务更短:减少锁持有与版本积累
- 批量操作分批提交:减少大事务
- 建立慢 SQL 治理机制 :
- 收集 -> 归因(计划、索引、数据分布)-> 优化 -> 回归验证
6.6 生产排障 Checklist
-
锁等待/死锁
- MySQL:
SHOW ENGINE INNODB STATUS \G→ 找 latest detected deadlock - PostgreSQL:
SELECT * FROM pg_locks l JOIN pg_stat_activity a ON a.pid = l.pid WHERE NOT granted;
- MySQL:
-
长事务扫描
- MySQL:
SELECT trx_id, trx_started, trx_query FROM information_schema.innodb_trx ORDER BY trx_started; - PostgreSQL:
SELECT pid, query_start, state, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY query_start;
- MySQL:
-
膨胀/Undo 累积
- MySQL:监控 undo tablespace, history list length
- PostgreSQL:监控
pg_stat_all_tables.n_dead_tup, autovacuum 日志
-
隔离级别误用
- 检查框架/ORM 是否显式降级到 READ UNCOMMITTED / READ COMMITTED 影响一致性
7. 迁移与兼容性对照
7.1 迁移的三件事:数据、语义、链路
- 数据(Data):全量怎么搬?增量怎么追?怎么校验?
- 语义(Semantics):隔离级别、时间/时区、字符集/collation、NULL/空串、大小写等差异会不会影响业务?
- 链路(Cutover):怎么切流量?怎么回滚?怎么演练?
7.2 迁移前先对齐的目标
数据一致性目标
- 是否允许停机迁移(离线)?
- 是否需要双写/增量同步(在线)?
- RPO/RTO 期望是什么?
功能语义目标
- MySQL 默认隔离级别/自增语义/字符串比较规则等,迁移后是否要完全一致?
- SQL 兼容层(应用/ORM/存储过程)改动范围多大?
7.3 数据类型映射(高频)
自增与主键
- MySQL :
AUTO_INCREMENT - PostgreSQL :推荐
GENERATED {ALWAYS|BY DEFAULT} AS IDENTITY(或历史上的serial/bigserial)
迁移注意:
- 是否需要保留原主键值?(多数情况需要)
- 自增序列是否要
setval()对齐到当前最大值?
字符串与排序规则(collation)
- MySQL:字符集/collation 对比较、排序、唯一性影响极大
- PostgreSQL:也受 collation 影响,但机制与可选项不同
迁移注意:
- 唯一索引是否会因为 collation 差异导致冲突(大小写/重音敏感性)?
- 业务是否依赖 MySQL 的某种"宽松比较"行为?
时间与时区(最容易踩坑)
- MySQL
DATETIME:无时区语义(通常按"业务时区"解释)TIMESTAMP:与时区/范围相关
- PostgreSQL
timestamp:无时区语义timestamptz:带时区语义(推荐理解并统一使用策略)
迁移注意:
- 明确"存 UTC 还是存本地时间"
- 所有服务与数据库会话时区必须统一
布尔、JSON、二进制
-
布尔
- MySQL:常见用
TINYINT(1)表示布尔 - PostgreSQL:原生
boolean
- MySQL:常见用
-
JSON
- MySQL:
JSON - PostgreSQL:
json/jsonb(多数场景用jsonb)
- MySQL:
7.4 常见 SQL 语法/函数差异(带示例)
分页
MySQL:
sql
SELECT * FROM users ORDER BY id LIMIT 100, 20;
PostgreSQL:
sql
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 100;
建议:大分页改 keyset pagination(两边通用)。
Upsert
MySQL:
sql
INSERT INTO t(id, v) VALUES (1, 'a')
ON DUPLICATE KEY UPDATE v = VALUES(v);
PostgreSQL:
sql
INSERT INTO t(id, v) VALUES (1, 'a')
ON CONFLICT (id) DO UPDATE SET v = EXCLUDED.v;
字符串拼接 / NULL 处理
MySQL:
sql
SELECT CONCAT(a, b), IFNULL(x, y);
PostgreSQL:
sql
SELECT a || b, COALESCE(x, y);
日期函数
MySQL:
sql
SELECT NOW(), DATE_FORMAT(NOW(), '%Y-%m-%d');
PostgreSQL:
sql
SELECT now(), to_char(now(), 'YYYY-MM-DD');
7.5 DDL 与约束语义差异(容易踩坑)
外键
- 两者都支持
- 迁移到 PostgreSQL 后启用外键可能暴露历史脏数据
建议:
- 迁移前先做数据清洗/对账
大小写与引用标识符
- PostgreSQL:未引用的标识符会折叠为小写;双引号引用会保留大小写并变得大小写敏感
- MySQL:大小写行为与系统/配置相关(尤其是表名在不同 OS 上)
建议:
- 迁移前统一命名规范(全小写 + 下划线)
7.6 在线迁移的常见思路(更可落地)
全量 + 增量(推荐的通用套路)
- 全量导入
- MySQL:导出 dump(或物理备份)
- PostgreSQL:pg_restore/批量 COPY 等
- 增量同步(CDC)
- 订阅 MySQL binlog 或 PG 的逻辑复制/WAL
- 追平与对账
- 追平延迟到可接受范围
- 切流量
- 短暂停写或双写窗口
- 观测与回滚窗口
双写(应用层/中间件层)
- 优点:控制力强,可逐步迁移
- 缺点:复杂度高,需要处理幂等、顺序、一致性与回放
7.7 校验与回滚(必须写进预案)
校验
- 行数对比(每表)
- 抽样比对(关键字段)
- 校验和/哈希(按分片范围)
- 业务对账(核心账务/订单链路)
回滚
- 明确切换窗口、回滚条件
- 回滚路径:
- DNS/VIP/Proxy 路由回切
- 应用配置回切
- 只读保护(防止双写污染)
总结
本文档系统对比了 MySQL(InnoDB) 与 PostgreSQL 在以下方面的核心差异:
- 事务与 MVCC:MySQL 默认 RR + next-key lock,PostgreSQL 默认 RC + SSI 串行化
- 索引策略:MySQL 依赖聚簇索引+覆盖索引,PostgreSQL 提供 B-Tree/GIN/GiST/BRIN 多种选择
- 复制与 HA:MySQL binlog 复制成熟,PostgreSQL WAL 流复制+逻辑复制更灵活
- 类型系统:PostgreSQL 类型+操作符+索引生态更强,特别是 jsonb + GIN
- 运维模型:PostgreSQL 需要理解 vacuum/bloat,MySQL 需要理解 buffer pool/undo/purge
- 迁移兼容:时区、collation、标识符大小写是最容易踩坑的地方
选型建议:
- 简单 OLTP + 团队熟悉度/读扩展成熟 → MySQL
- 复杂查询/类型多样/强一致与平台化扩展 → PostgreSQL
核心实践原则:
- 控制事务长度
- 合理使用索引
- 理解复制延迟
- 建立慢 SQL 治理机制
- 迁移前充分测试与对账
本文档基于实际生产经验整理,建议结合具体业务场景和团队能力进行选型与优化。如有疑问或补充,欢迎交流讨论。