数据库演化:MySQL → PostgreSQL → 分布式,从单机到 NewSQL 的全链路实战
Java Web 微服务系列 · 第 8 篇 · 数据库演化 阅读时长:约 45 分钟 本文写于 2026 年 6 月
引子:MySQL 第一次"卡死"的那个深夜
2017 年某日凌晨 3 点,我被一阵急促的电话铃声吵醒。电话那头是刚入职不到半年的运维小哥,声音发颤:"哥,订单服务挂了,DB 连接全爆,整个集群处于不可用状态。"
我一边穿衣服一边远程连上服务器,看到的景象触目惊心:
- InnoDB buffer pool 利用率 99.8%,已经被锁页撕扯(lock wait)拖到 12 秒
- 慢查询日志 在 5 分钟内涌进 1.2 万条
SELECT * FROM order WHERE user_id = ? AND status IN (...)的全表扫 - 从库延迟 飙到 30 分钟,主从复制的 relay log 把磁盘写满
- 连接数 跑到 3500/3500,新请求全部被拒绝
事后排查,这是一张 4.2 亿行的 order 表,单表行数已经远超 InnoDB 的最优承载范围。代码里没有按 user_id 建索引,业务员按状态查询导致 90% 的查询全表扫。
那次事故直接损失 38 万元。我开复盘会时说过一句后来被团队当成内部黑话的话:"不是 MySQL 不行,是 MySQL 在裸奔"。
从那以后,我们走上了"数据库演化“这条路:
- 第一步:MySQL 索引优化 + 慢查询治理(4.2 亿行也能跑)
- 第二步:主从分离 + 读写分离(扛住 5 万 QPS)
- 第三步:分库分表(订单表按用户 ID 拆 64 库 × 64 表)
- 第四步:PostgreSQL 上位(复杂报表场景)
- 第五步:TiDB 试点(解决 MySQL 容量天花板)
到 2025 年回头看,数据库演化就是 Java 微服务架构演化的"地基”——没有它,上层的弹性扩容、服务治理、流量调度全是空中楼阁。
本文要回答四个问题:
- MySQL 怎么"榨干最后一滴性能"?(索引 + 参数 + 架构)
- 什么时候必须升级到 PostgreSQL?
- 分库分表和分布式数据库怎么选?
- 容灾备份的底线在哪里?
全文 10000+ 字,一次把数据库演化的全链路讲透。
一、核心概念:把"主从/集群/分库分表/分布式"掰开
谈具体技术前,先把 6 个常被混用的术语定义清楚——很多团队的"分库分表"和"分布式数据库"压根不是一回事。
1.1 单机(Standalone)
单机:所有数据 + 所有请求都在一台服务器上。
| 维度 | 表现 |
|---|---|
| 容量上限 | 单盘 10TB、单库行数 5 亿(B+Tree 树高 4) |
| QPS 上限 | 1-3 万(写),5-10 万(读) |
| 可用性 | 单点,故障 = 业务停 |
| 适用场景 | Demo、测试、内部工具、小型 SaaS |
🎯 避坑点:单机能撑多久
经验值(InnoDB + 良好索引):
- 单表行数 < 1000 万:完全无压力
- 1000 万 - 5000 万:需要索引优化
- 5000 万 - 5 亿:必须考虑分表
- > 5 亿:分库分表 or 分布式数据库
5000 万不是"硬指标",但超过了就别再用单表硬抗。
1.2 主从复制(Master-Slave Replication)
主从复制:一台主库负责写,多台从库同步数据、负责读。
| 维度 | 表现 |
|---|---|
| 容量上限 | 单机相同,但通过分库可扩 |
| QPS 上限 | 读线性扩展(10 台从 = 50 万 QPS) |
| 写能力 | 仍是单机(最大瓶颈) |
| 可用性 | 主库故障需人工切换,分钟级恢复 |
| 同步延迟 | 异步复制通常 10-100ms |
💡 原理:主从复制的"三种姿势"
- 异步复制(默认):主库写完 binlog 就返回,从库异步拉。快但不安全——主库挂掉时未同步的 binlog 会丢
- 半同步复制(MySQL 5.5+):主库等至少 1 个从库确认收到 binlog 再返回。安全但慢(多 1 次 RTT)
- 增强半同步(MySQL 5.7.4+):从库持久化到 relay log 后才算确认。比半同步再安全一层
金融场景必须增强半同步 + GTID;普通业务半同步够用。
1.3 集群(Cluster)
集群:多个节点共同承担同一个数据库的读写,对应用看起来像一台。
| 集群类型 | 代表 | 特点 |
|---|---|---|
| MySQL Group Replication(MGR) | MySQL 官方 | 单主或多主,强一致,写性能差 |
| MySQL NDB Cluster | MySQL 官方 | 内存数据库,实时性极强 |
| Percona XtraDB Cluster(PXC) | Percona | 基于 Galera,同步复制 |
| Galera Cluster for MySQL | Codership | 同步复制,多主写入 |
| PostgreSQL Patroni + etcd | Zalando | 高可用方案 |
💡 原理:集群 vs 主从的本质区别
- 主从:从库只读,主库单点写。扩读不扩写
- 集群:所有节点都可读可写。读写都扩
- 代价:集群的"同步复制"会拖慢写入(每写一次要等所有节点确认)
PXC 写性能约为主从的 30%-50%。如果写为主,集群不是好选择。
1.4 读写分离(Read-Write Splitting)
读写分离:在主从基础上,让写走主库、读走从库,对应用透明。
常见中间件:
- ShardingSphere-JDBC(推荐):客户端嵌入,零依赖,性能好
- MyCat:代理模式,独立部署,跨语言
- Atlas:奇虎 360 开源,已停更
- MaxScale:MariaDB 出品
1.5 分库分表(Sharding)
分库分表:把一个库 / 一张表的数据,按规则拆分到多个库 / 多张表。
| 拆分方式 | 说明 |
|---|---|
| 垂直分库 | 按业务拆(订单库 / 用户库 / 商品库) |
| 垂直分表 | 把宽表按列拆(主表 + 扩展表) |
| 水平分库 | 同一张表的数据按规则拆到不同库 |
| 水平分表 | 同一张表的数据按规则拆到同库不同表 |
1.6 分布式数据库(Distributed SQL / NewSQL)
分布式数据库:原生支持水平扩展的数据库,对应用像单机一样使用。
| 数据库 | 类型 | 一致性协议 | 特点 |
|---|---|---|---|
| TiDB | NewSQL | Raft | 兼容 MySQL 协议,HTAP |
| OceanBase | 原生分布式 | Paxos 多副本 | 阿里自研,2010 立项 |
| PolarDB | 存储计算分离 | Parallel Raft | 阿里云原生 |
| CockroachDB | NewSQL | Raft | 兼容 PostgreSQL 协议 |
| YugabyteDB | NewSQL | Raft | 兼容 PostgreSQL/Cassandra |
| Citus | PG 扩展 | - | 把 PG 变成分布式 |
🎯 避坑点:分库分表 ≠ 分布式数据库
维度 分库分表 分布式数据库 兼容性 需改造 SQL 兼容 MySQL/PG 协议 跨库 JOIN 不支持 支持(通过下推) 跨库事务 需 Seata 天然支持(Percolator/TCC) 运维 复杂(数据迁移、扩容) 较简单(自动 Rebalance) 成本 中等 较高(多副本) 选型时机 1000 万 - 100 亿行 100 亿 + 行 分库分表是"穷人版"分布式数据库——成本低但改造大。
二、选型史:MySQL → PostgreSQL → NewSQL 的三次跃迁
数据库的演化不是一夜之间完成的。从 1995 年 MySQL 1.0 发布到 2015 年 TiDB 开源,20 年时间完成了三次关键跃迁。
2.1 三次跃迁时间线
2.2 第一次跃迁:从 MySQL 到 PostgreSQL
触发场景:复杂查询、GIS、JSON、CTE 等高级特性需求。
| 维度 | MySQL 8.0 | PostgreSQL 16 |
|---|---|---|
| SQL 兼容性 | 中等 | 强(最接近 SQL 标准) |
| JSON 支持 | JSON 类型 | JSONB(二进制索引) |
| GIS 支持 | 基础 | PostGIS(业界标杆) |
| CTE / 窗口函数 | 8.0 才开始 | 早就支持 |
| 全文检索 | 基础 | 丰富(多语言、权重) |
| 物化视图 | 8.0 GA | 支持 |
| 复杂查询性能 | 中 | 强(优化器好) |
| 写入性能 | 强 | 中 |
| 复制 | binlog | 流复制 + 逻辑复制 |
| 运维生态 | 丰富(国内首选) | 中等(专业领域首选) |
| 适合场景 | 高并发 OLTP | OLAP + 复杂业务 |
📌 实践:什么时候该迁到 PG
- 业务里有 GIS(地图、附近的人、配送范围)
- JSON 字段需要查询(半结构化数据)
- 复杂报表(窗口函数、CTE、递归查询)
- 全文检索要求高(多语言、模糊搜索)
- 严格 SQL 规范(金融、政府)
不是性能问题,而是特性问题。MySQL 5.7 之后性能差距已大幅缩小。
2.3 第二次跃迁:从 MySQL 主从到分库分表
触发场景:单库行数超 5 亿,QPS 持续走高,写入瓶颈出现。
代表事件:
- 2012 年:淘宝"去 IOE",MySQL + 自主中间件取代 Oracle
- 2014 年:阿里 OceanBase 在双 11 承担核心交易库
- 2016 年:Sharding-JDBC(当当,后并入 ShardingSphere)成为事实标准
- 2018 年:MyCat 流行,代理式分库分表方案成熟
2.4 第三次跃迁:从分库分表到 NewSQL
触发场景:分库分表的运维成本已经超过数据库本身的成本。
| 痛点 | 表现 |
|---|---|
| 扩容噩梦 | 32 库扩到 64 库要全量数据迁移 |
| 跨库 JOIN | 性能差,业务层要做内存 JOIN |
| 分布式事务 | 强一致事务需 Seata,复杂 |
| 运维复杂 | 几百个分片,几百个 binlog 链路 |
| 研发效率 | SQL 受限,需求 30% 时间在做"分库适配" |
应对:用 NewSQL 把分库分表的"中间件逻辑"下沉到数据库内部。
| 数据库 | 立项时间 | 开源时间 | 关键节点 |
|---|---|---|---|
| OceanBase | 2010 阿里 | 2019(OB 1.0) | 2014 双 11 抗住 8 万笔/秒 |
| TiDB | 2015 PingCAP | 2017(GA) | 2018 京东"跑赢"分库分表 |
| CockroachDB | 2015 Cockroach Labs | 2017 | 2021 22.1 GA |
| YugabyteDB | 2016 Yugabyte | 2018 | 2.0 GA(PG 兼容) |
| PolarDB | 2015 阿里云 | 闭源 | 2018 云原生 + 存储计算分离 |
💡 原理:NewSQL 的核心创新
- 计算-存储分离:SQL 层无状态,可无限扩容
- 数据自动分片(Auto-Sharding):应用不感知
- 多副本强一致:Paxos / Raft 协议保证
- HTAP 混合负载:一份数据支持 OLTP + OLAP
本质是用分布式协议换"无限扩展"。
三、为什么必须演化:4 大驱动力
3.1 驱动力一:数据量爆炸
来自 IDC 的预测:
- 2010 年:全球数据量 1.2 ZB
- 2020 年:59 ZB(5 年涨 50 倍)
- 2025 年:175 ZB
- 2030 年:612 ZB
企业级数据库的"舒适区"是 1TB-5TB 单库。超过这个量级,单机的物理极限就开始显现:
- B+Tree 树高 > 4,索引深度查询变慢
- 备份/恢复时间线性增长(10TB 恢复 8 小时+)
- 主从延迟上升(大事务、批量更新)
- 运维窗口变窄(DDL 时间从分钟级到小时级)
3.2 驱动力二:并发量陡增
| 业务阶段 | QPS | 数据库形态 |
|---|---|---|
| 早期 | < 1 万 | 单机 MySQL |
| 成长期 | 1-10 万 | 主从 + 读写分离 |
| 爆发期 | 10-100 万 | 分库分表 + 缓存 |
| 巨型业务 | > 100 万 | 分布式数据库 + 多级缓存 |
🎯 避坑点:不要过早分库分表
分库分表的成本:
- 业务改造(30% 工期)
- 跨库 JOIN 性能损失(10-100 倍)
- 分布式事务(20% 性能损耗)
- 运维复杂度 ×3
过早分库分表 = 把工程能力浪费在"应对未来"。先看实际数据量:单表 > 5000 万行再考虑。
3.3 驱动力三:可用性要求
业务连续性分级:
| 等级 | 业务类型 | RTO | RPO | 推荐方案 |
|---|---|---|---|---|
| P0 | 核心交易 | < 5 分钟 | < 1 分钟 | 异地多活 |
| P1 | 重要业务 | < 30 分钟 | < 5 分钟 | 同城双活 + 异地灾备 |
| P2 | 一般业务 | < 数小时 | < 1 小时 | 同城灾备(热备) |
| P3 | 后台系统 | < 1 天 | < 1 天 | 主从副本即可 |
MySQL 主从最多撑到 P2。要做到 P0 的 RTO < 5 分钟,必须异地多活 + 分布式数据库。
3.4 驱动力四:合规与云原生
- 强监管行业(金融、医疗):要求两地三中心 + 同城双活
- GDPR / 等保 2.0:数据本地化 + 可审计
- 云原生:要求弹性扩缩 + 按量计费 + 跨可用区容灾
自建 MySQL 越来越难满足这些要求,必须演进到云原生数据库(RDS、PolarDB、Aurora)。
四、MySQL 深度优化
在谈"要不要换数据库"之前,先把 MySQL 榨干——大多数团队连 MySQL 8.0 的 50% 性能都没用到。
4.1 存储引擎选择
MySQL 8.0 已经全面 InnoDB,MyISAM 只用于只读历史表。
| 引擎 | 锁粒度 | 事务 | 崩溃恢复 | 适用 |
|---|---|---|---|---|
| InnoDB | 行锁 | 支持 | Redo + Undo Log | 99% 场景 |
| MyISAM | 表锁 | 不支持 | 无 | 只读历史表 |
| Memory | 表锁 | 不支持 | 断电丢 | 临时表 |
| Archive | 行锁 | 不支持 | 无 | 日志归档 |
4.2 索引原理:B+Tree 是什么
B+Tree 是 InnoDB 的核心数据结构。
| B+Tree 特性 | 影响 |
|---|---|
| 数据都在叶子节点 | 查询路径长度固定 |
| 叶子节点双向链表 | 范围查询 O(log N) |
| 非叶子节点只存键值 | 单页能放更多索引项 |
| 树高 3-4 层 | 千万级数据查询 < 4 次 IO |
B+Tree 的物理结构(InnoDB 16KB 页):
- 每页 16KB(
innodb_page_size,默认) - 每页最多 7992 行(行最小 2 字节,最大 8KB)
- 页内查找用二分:Page Directory 槽位记录偏移
页分裂(Page Split):
随机插入 / 自增主键满时,InnoDB 会做页分裂——把一页拆成两页。页分裂 = 性能杀手:
- 拆页期间要加锁
- 产生磁盘碎片
- 顺序写变成随机写
对策:
- 主键用自增 ID(顺序写,几乎不分裂)
- 不要用 UUID(随机写,频繁分裂)
- 提前预留空间:
innodb_fill_factor(8.0)
💡 原理:为什么 B+Tree 而不是 B-Tree 或 Hash
- Hash 索引:等值查询 O(1),但不支持范围查询、不支持排序
- B-Tree:非叶子节点也存数据,范围查询要回根节点
- B+Tree:数据全在叶子,叶子双向链表,范围查询一次扫描即可
MySQL InnoDB 的自适应哈希索引(AHI)是对 B+Tree 的"局部优化"——把热点页加载到内存 Hash。
4.3 索引优化的 12 条法则
法则 1:最左前缀原则
联合索引 (a, b, c) 相当于建了 (a)、(a, b)、(a, b, c) 三个索引,但不能跳过 a。
| |
法则 2:避免在索引列上做计算
| |
法则 3:字符串前缀索引
| |
法则 4:覆盖索引(Covering Index)
| |
法则 5:OR 改 UNION
| |
法则 6:IN 控制在 1000 以内
| |
法则 7:避免 SELECT *
| |
法则 8:NOT IN 改 NOT EXISTS
| |
法则 9:连接字段加索引
| |
法则 10:避免深分页
| |
法则 11:批量写入
| |
法则 12:定期 ANALYZE TABLE
| |
4.4 EXPLAIN 执行计划全解
EXPLAIN 是 MySQL 优化的"听诊器"。
| |
| 字段 | 含义 | 优化目标 |
|---|---|---|
| id | SELECT 序号 | 越大越先执行 |
| select_type | 类型 | SIMPLE > PRIMARY > UNION > SUBQUERY |
| type | 访问类型 | system > const > eq_ref > ref > range > index > ALL |
| possible_keys | 可能用的索引 | 多个候选 |
| key | 实际用的索引 | NULL = 全表扫 |
| key_len | 索引长度 | 越短越好 |
| rows | 扫描行数(估算) | 越小越好 |
| Extra | 额外信息 | 关注 Using filesort / Using temporary |
type 字段 7 个等级(性能从高到低):
| |
| type | 含义 | 示例 |
|---|---|---|
| system | 系统表 | SELECT * FROM mysql.proxied_user |
| const | 主键 / 唯一索引等值 | SELECT * FROM t WHERE id = 1 |
| eq_ref | JOIN 主键 / 唯一索引 | t1 JOIN t2 ON t1.id = t2.id |
| ref | 非唯一索引 | SELECT * FROM t WHERE name = ’tom' |
| range | 索引范围 | WHERE id > 100 AND id < 200 |
| index | 全索引扫 | SELECT id FROM t(只查索引列) |
| ALL | 全表扫 | SELECT * FROM t WHERE name LIKE ‘%tom%’ |
📌 实践:EXPLAIN 看什么
- type 是不是 ALL——出现 ALL 必须优化
- rows 是不是过大——> 1000 行要警觉
- Extra 是不是有 filesort / temporary——出现就考虑加索引
- key 是不是 NULL——没走索引
- key_len 是不是太小——联合索引没用到前列
4.5 MySQL 参数调优
不要照搬网上参数——要根据实际业务、机器配置、QPS 量级调。
内存相关(最关键)
| 参数 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
innodb_buffer_pool_size | 128MB | 物理内存 50-70% | InnoDB 缓存池 |
innodb_log_buffer_size | 16MB | 64-256MB | redo log 缓冲 |
key_buffer_size | 8MB | 32MB | MyISAM 索引缓冲(基本无用) |
tmp_table_size | 16MB | 64-256MB | 临时表 |
max_heap_table_size | 16MB | 64-256MB | MEMORY 引擎表 |
sort_buffer_size | 256KB | 1-4MB | 排序缓冲 |
join_buffer_size | 256KB | 1-4MB | JOIN 缓冲 |
read_buffer_size | 128KB | 1-2MB | 全表扫读缓冲 |
连接相关
| 参数 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
max_connections | 151 | 1000-5000 | 最大连接数 |
max_user_connections | 0 | 0 | 单用户最大连接 |
wait_timeout | 28800 | 600 | 空闲连接超时(秒) |
interactive_timeout | 28800 | 600 | 交互超时 |
InnoDB 写入相关
| 参数 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
innodb_flush_log_at_trx_commit | 1 | 1 / 2 | 1=最安全;2=折中(每 1s 刷盘) |
sync_binlog | 1 | 1 / 0 | 1=安全;0=性能 |
innodb_flush_method | - | O_DIRECT | 绕过 OS Cache |
innodb_io_capacity | 200 | 2000(SSD) | 刷盘 IO 能力 |
🛑 误区警示:
innodb_flush_log_at_trx_commit=2是不是不安全?不是。= 2 表示"每秒刷一次盘",最多丢 1 秒数据。 大多数业务(订单、用户)能接受 1 秒数据丢失以换取 10 倍写入性能。 金融场景必须 = 1。
二进制日志
| 参数 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
binlog_format | ROW | ROW | 推荐 ROW(数据一致) |
binlog_row_image | FULL | FULL / MINIMAL | MINIMAL 减小 binlog |
expire_logs_days | 0 | 7-30 | 自动清理 |
max_binlog_size | 1GB | 512MB-1GB | 单文件大小 |
💡 原理:binlog_format 怎么选
- STATEMENT(默认):记录 SQL 语句。日志小,但有主从一致性问题(如
NOW())- ROW:记录行变更。日志大但绝对一致。生产推荐 ROW
- MIXED:自动判断。别用,行为不可预期
4.6 MySQL 监控指标
SRE 必看 7 大指标:
| 指标 | 阈值 | 监控工具 |
|---|---|---|
| QPS / TPS | < 80% 设计上限 | Prometheus + mysqld_exporter |
| 连接数使用率 | < 80% | mysqld_exporter |
| InnoDB buffer pool 命中率 | > 99% | 命中率 = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) |
| 慢查询数量 | < 0.1% 总查询 | slow_query_log + pt-query-digest |
| 主从延迟 | < 1 秒 | Seconds_Behind_Master |
| 锁等待 | 0 长锁 | SHOW ENGINE INNODB STATUS |
| 磁盘 IO 利用率 | < 70% | iostat |
🎯 避坑点:buffer pool 命中率
命中率 < 99% 通常意味着热点数据 > buffer pool。 不要急着加内存——先看:
- 是不是有全表扫占用了 buffer pool(
SHOW ENGINE INNODB STATUS看 LRU 列表)- 是不是有 SELECT * 拉大对象
- 是不是表数据本身远大于内存
盲加内存是治标不治本。
五、PostgreSQL 何时上位 + 杀手特性
5.1 何时该迁到 PG
MySQL 没死,但有些场景 MySQL 就是干不过 PG。
场景 1:复杂 SQL 报表
| |
场景 2:GIS 地理信息
| |
场景 3:半结构化数据
| |
场景 4:递归查询
| |
场景 5:严格一致性
| |
5.2 PG 杀手特性详解
5.2.1 JSONB vs MySQL JSON
| 维度 | MySQL JSON | PG JSONB |
|---|---|---|
| 存储格式 | 文本 | 二进制(解析快) |
| 索引 | 虚拟列 | 原生 GIN 索引 |
| 查询语法 | JSON_EXTRACT | ->> -> @> |
| 性能 | 一般 | 快 2-10 倍 |
5.2.2 MVCC 的优势
MVCC(Multi-Version Concurrency Control):
- MySQL InnoDB:通过 undo log 实现,长事务会膨胀 undo 表空间
- PostgreSQL:每行存两个 xmin/xmax,老版本在 vacuum 时清理
📌 实践:autovacuum 调优
PG 的 MVCC 优势是"读不阻塞写、写不阻塞读"。 但老版本数据不清理会膨胀。autovacuum 必须调好:
参数 默认 建议 说明 autovacuum_vacuum_scale_factor0.2 0.05-0.1 触发 vacuum 的死行比例 autovacuum_analyze_scale_factor0.1 0.05 触发 analyze 的变更比例 autovacuum_vacuum_cost_limit200 1000-2000 vacuum 速度 autovacuum_max_workers3 4-8 并发 worker 数 大表要单独配 autovacuum:
ALTER TABLE big_table SET (autovacuum_vacuum_scale_factor = 0.02)
5.2.3 复制方案
PG 有 3 种复制:
| 复制方式 | 说明 | 用途 |
|---|---|---|
| 流复制(Streaming Replication) | 基于 WAL,类似 binlog | 高可用、读写分离 |
| 逻辑复制(Logical Replication) | 基于发布订阅,跨版本 | 数据迁移、聚合 |
| 级联复制(Cascading Replication) | 多级从库 | 异地灾备 |
| |
5.2.4 分区表
PG 10+ 原生分区表:
| |
5.3 PG 参数调优
内存相关
| 参数 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
shared_buffers | 128MB | 物理内存 25% | PG 缓存池 |
work_mem | 4MB | 32-256MB | 排序/哈希缓冲 |
maintenance_work_mem | 64MB | 512MB-2GB | vacuum / CREATE INDEX |
effective_cache_size | 4GB | 物理内存 50-75% | 给优化器的"提示" |
huge_pages | try | try | 大内存机器开启 |
WAL 相关
| 参数 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
wal_level | replica | replica / logical | logical 启用逻辑复制 |
max_wal_size | 1GB | 4-16GB | WAL 总大小 |
min_wal_size | 80MB | 1-2GB | WAL 最小保留 |
checkpoint_completion_target | 0.5 | 0.9 | 减少 checkpoint 抖动 |
🎯 避坑点:work_mem 调多大
work_mem是每个操作的内存,不是全局- 一个复杂查询可能有 10 个 sort 节点 → 实际占用 10 × work_mem
- 1000 个并发连接 × work_mem 256MB = 256GB 内存需求
- 生产建议 32-64MB,复杂查询用
SET LOCAL work_mem = '1GB';临时调大
六、主从复制 + 读写分离
6.1 复制原理
三步流程:
- 主库:所有数据变更写 binlog(ROW 模式)
- 从库 IO 线程:拉主库 binlog,写入本地 relay log
- 从库 SQL 线程:重放 relay log 到从库
6.2 复制模式
| 模式 | 延迟 | 安全性 | 适用 |
|---|---|---|---|
| 异步(默认) | 10-100ms | 主库挂丢数据 | 普通业务 |
| 半同步 | +1 RTT | 主库挂最多丢 1 秒 | 重要业务 |
| 增强半同步 | +1-2 RTT | 主库挂不丢 | 金融 |
SHOW SLAVE STATUS 关键指标(MySQL 8.0 后是 SHOW REPLICA STATUS):
| 字段 | 期望 | 异常处理 |
|---|---|---|
Slave_IO_Running: Yes | 必须 Yes | No → 网络/账号问题 |
Slave_SQL_Running: Yes | 必须 Yes | No → SQL 线程挂了 |
Seconds_Behind_Master: 0 | 0 | > 0 → 主从延迟 |
Master_Log_File / Read_Master_Log_Pos | 持续增长 | 不动 → IO 线程有问题 |
Relay_Master_Log_File / Exec_Master_Log_Pos | 持续增长 | 不动 → SQL 线程卡住 |
Last_Error | 空 | 非空 → 看错误码 |
GTID 模式(强烈推荐)
GTID = Global Transaction Identifier,每个事务全局唯一。
| |
💡 原理:GTID 解决什么
不开启 GTID 的痛点:
- 主从切换需要找 binlog file + position(容易错)
- 级联复制链路复杂(A → B → C,B 故障时 C 找不到位点)
- 跳过一个错误要手动算位点
开启 GTID 的好处:
- 自动定位,无需 file + position
- 跳过错误简单:
SET GTID_NEXT='xxx'; BEGIN; COMMIT; SET GTID_NEXT='AUTOMATIC';- 故障切换几乎是零配置
6.3 主从延迟的 7 种解决方案
主从延迟是读写分离最大的坑。常见解法:
| 方案 | 原理 | 适用 |
|---|---|---|
| 强制读主 | 写后立即读主库 | 强一致场景 |
| Session 标记 | 同一个 session 写后短时间走主 | 简单业务 |
| 缓存标记 | 写时在缓存记录 N 秒"读主"标记 | 通用 |
| 查主库判断 | 读从前先查"是否最近写过" | 极端场景 |
| GTID 等待 | 从库等到指定 GTID 应用完 | MySQL 5.7+ |
| 延迟监控 + 告警 | 延迟 > 阈值告警 | 必须有 |
| 业务回避 | 不做读写分离 | 一致性要求极高 |
最推荐"缓存标记法":
| |
6.4 读写分离中间件对比
| 中间件 | 模式 | 性能 | 复杂度 | 适用 |
|---|---|---|---|---|
| ShardingSphere-JDBC | 客户端 jar | 最优(无网络跳) | 低 | Java 应用首选 |
| ShardingSphere-Proxy | 代理 | 中 | 中 | 跨语言 |
| MyCat | 代理 | 中 | 高 | 复杂分库分表 |
| MaxScale | 代理 | 中 | 中 | MariaDB |
| ProxySQL | 代理 | 中 | 中 | MySQL |
📌 实践:选型建议
- 纯 Java 应用:ShardingSphere-JDBC(性能最好)
- 多语言应用:ShardingSphere-Proxy
- 只用读写分离,不分库分表:ProxySQL / 自研 AOP
七、分库分表
7.1 何时分库分表
经验阈值(InnoDB + 良好索引):
| 指标 | 阈值 | 风险 |
|---|---|---|
| 单表行数 | 5000 万 | 查询性能下降 |
| 单表容量 | 50GB | DDL 变慢 |
| 单库 QPS | 1-3 万 | CPU 打满 |
| 写并发 | 5000 TPS | 主从延迟 |
🎯 避坑点:分库分表不是"应不应该",是"什么时候"
- < 1000 万行:坚决不分
- 1000 万 - 5000 万:加索引 + 优化 SQL 还能撑
- 5000 万 - 5 亿:分库分表
- > 5 亿:直接上分布式数据库
提前分库分表是过早优化,推迟分库分表是踩坑。
7.2 拆分维度
垂直拆分
垂直分库:按业务边界拆。最简单、最有用,应该第一个做。
垂直分表:宽表拆主表 + 扩展表。
| |
水平拆分
水平分库:同一张表的数据按规则分到不同库。 水平分表:同一张表的数据按规则分到同库不同表。
7.3 Sharding 策略
| 策略 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| Hash 取模 | 分布均匀 | 扩容要数据迁移 | 通用 |
| Range 范围 | 不迁移老数据 | 热点(最新数据访问多) | 时序数据 |
| List 列表 | 业务清晰 | 数据不均 | 业务隔离 |
| 地理分片 | 用户延迟低 | 跨区数据不一致 | O2O |
| 一致性 Hash | 扩容只影响 1/N | 复杂度高 | 大规模 |
Hash 取模最常用:
| |
7.4 分布式主键
分库分表后,自增 ID 失效。常见方案:
| 方案 | 性能 | 全局唯一 | 推荐度 |
|---|---|---|---|
| UUID | 优 | 是 | 缺点:占空间、无序 |
| Snowflake(雪花算法) | 优 | 是 | 推荐 |
| 美团 Leaf | 优 | 是 | 推荐 |
| 数据库号段 | 中 | 是 | 中小厂 |
| Redis incr | 中 | 是 | 中小厂 |
Snowflake 64 位结构:
| |
每毫秒可生成 4096 个 ID,每秒 409.6 万。
| |
7.5 跨库查询 + 跨库事务
跨库查询
分库分表最大的代价:
| |
解决方案:
| 方案 | 思路 | 适用 |
|---|---|---|
| 业务层 JOIN | 先查 order 拿到 user_id,再查 user | 简单但要 2 次查询 |
| 宽表冗余 | order 表冗余 username 字段 | 适合读多写少 |
| 搜索引擎 | 全量同步到 ES,在 ES 查 | 适合复杂查询 |
| 数据仓库 | T+1 同步到 Hive / ClickHouse | 适合 OLAP |
| 广播表 | 小表(字典)全库冗余 | 字典类 |
跨库事务
分布式事务的 4 种方案(已在前作展开):
| 方案 | 一致性 | 性能 | 复杂度 |
|---|---|---|---|
| 2PC | 强 | 差 | 高 |
| TCC | 强 | 中 | 高 |
| Saga | 最终 | 好 | 中 |
| 本地消息表 | 最终 | 好 | 低 |
Seata AT 模式:业界主流
Seata = Simple Extensible Autonomous Transaction Architecture,阿里开源。AT 模式是其默认工作模式:
AT 模式的"魔法":
- 第一阶段:执行业务 SQL,生成 undo log(前镜像),不提交
- 第二阶段:TC 看所有分支都成功 → 全局提交;任一失败 → 用 undo log 回滚
- 应用层无感知:基于数据源代理(
DataSourceProxy)自动接管
AT 模式的限制:
- 必须是关系型数据库
- SQL 必须是单条(不支持跨 SQL 事务)
- 全局锁依赖 TC,TC 单点问题
🛑 误区警示:尽量不用分布式事务
能用业务回避就别用事务。
- 把"扣库存 + 下订单"拆成"先预占库存再下单 + 异步扣库存"
- 用最终一致性 + 对账保证正确性
- 99% 的分布式事务场景可以用业务设计绕开
7.6 ShardingSphere 实战
ShardingSphere 5.x 是当前 Java 生态首选。
| |
八、NewSQL 三剑客
8.1 TiDB 详解
TiDB = MySQL 协议 + 分布式架构。
| 组件 | 作用 |
|---|---|
| TiDB Server | SQL 层,无状态,可横向扩 |
| PD(Placement Driver) | 集群调度 + TSO 时间戳 |
| TiKV | 存储层,基于 RocksDB + Raft |
| 优势 | 说明 |
|---|---|
| MySQL 兼容 | 95% 场景无需改代码 |
| 水平扩展 | 加 TiKV 节点即扩 |
| 强一致 | Raft + Percolator 事务 |
| HTAP | TiFlash 列存引擎支持 OLAP |
| 自动 Rebalance | 不停服迁移数据 |
| 劣势 | 说明 |
|---|---|
| 运维复杂 | 至少 6 个组件(3 TiDB + 3 PD + 3 TiKV + TiFlash) |
| 大事务支持差 | 单事务 < 100MB 限制 |
| 二级索引 | 全局索引有性能开销 |
| 成本 | 3 副本起步,资源消耗 3 倍 |
8.2 OceanBase 详解
OceanBase = 原生分布式 + 金融级。
| 优势 | 说明 |
|---|---|
| Paxos 多副本 | 强一致 + 高可用 |
| Oracle 兼容 | 替代 Oracle |
| HTAP | 同一份数据 OLTP + OLAP |
| 多模 | 兼容 MySQL、Oracle、PG |
关键事件:
- 2010:阿里立项
- 2014:双 11 抗住 8 万笔/秒
- 2019:OceanBase 1.0 开源
- 2020:TPC-C 基准测试 7.07 亿 tpmC(世界第一)
- 2023:OceanBase 4.0 GA
8.3 PolarDB 详解
PolarDB = 存储计算分离。
核心创新:
- 共享存储:所有节点挂同一份数据,读节点无延迟
- RDMA 网络:节点间通信 < 100us
- 并行回放:从库并行重放 redo log
8.4 选型决策树
8.5 一致性协议速览
| 协议 | 特点 | 代表 |
|---|---|---|
| Paxos | 经典,难懂 | OceanBase、Chubby |
| Raft | 易懂的 Paxos | TiKV、etcd、CockroachDB |
| ZAB | ZK 专属 | ZooKeeper |
| EPaxos | Paxos 改进 | 学术 |
| Parallel Raft | Raft 并行 | PolarDB |
💡 原理:Raft 怎么保证一致
- Leader 选举:多数派投票选主
- 日志复制:Leader 接收写,复制到多数 Follower
- 提交时机:多数派落盘才提交
- 脑裂解决:通过 Term(任期号)防旧 Leader 复活
九、容灾 + 备份
9.1 容灾等级
| 等级 | 描述 | RTO | RPO | 成本 |
|---|---|---|---|---|
| L0 | 无备份 | - | - | 0 |
| L1 | 本地备份 | 数小时 | 数小时 | 1x |
| L2 | 本地 + 异地备份 | 数小时 | 数小时 | 1.2x |
| L3 | 热备(同城) | 分钟 | 0 | 2x |
| L4 | 双活(同城) | 秒 | 0 | 3-4x |
| L5 | 双活(异地) | 秒 | 秒 | 5-8x |
| L6 | 多活(异地) | 秒 | 0 | 10-15x |
9.2 备份策略
3-2-1 备份铁律:
- 3 份数据副本
- 2 种存储介质(如本地 + 异地)
- 1 份离线(防勒索病毒)
物理备份 vs 逻辑备份
| 维度 | 物理备份 | 逻辑备份 |
|---|---|---|
| 工具 | xtrabackup / pg_basebackup | mysqldump / pg_dump |
| 速度 | 快(直接拷文件) | 慢(逐行转 SQL) |
| 压缩 | 高 | 中 |
| 恢复速度 | 快 | 慢 |
| 跨版本 | 不支持 | 支持 |
| 跨平台 | 受限 | 不受限 |
| 适用 | 大库(> 100GB) | 小库、结构迁移 |
9.3 xtrabackup 实战
xtrabackup 是 MySQL 物理备份的首选工具(Percona 开源)。
xtrabackup 工作原理(关键 4 步):
xtrabackup 的"魔法"是:备份期间不锁库——通过持续跟踪 redo log 拿到一致性快照。这比 mysqldump 在线备份强 10 倍。
| |
📌 实践:xtrabackup 备份策略
- 全量备份:每周一次(业务低峰期)
- 增量备份:每天一次
- binlog 备份:实时(用 mysqlbinlog 或复制到远程)
- 保留期:全量 30 天,增量 7 天,binlog 14 天
9.4 PITR(Point-in-Time Recovery)
PITR = 时间点恢复。从全量备份 + binlog 恢复到任意时间点。
| |
9.5 PostgreSQL 备份
| |
9.6 备份验证:恢复演练
最关键的步骤——备份没演练 = 不存在。
演练清单:
- 每月一次:全量备份恢复演练(人工)
- 每季度一次:PITR 演练(恢复到指定时间点)
- 每年一次:跨机房灾备切换演练
- 每季度一次:核对备份文件大小、时间、压缩率
- 每月一次:检查备份脚本日志,告警是否正常
🛑 误区警示:这些坑别踩
- 备份完从不验证——某公司 3 年备份全部损坏
- 备份和数据库在同一块盘——磁盘坏了备份也没了
- 没做异地备份——机房故障 = 全丢
- 没加密——备份文件泄露 = 数据泄露
- 没做定期恢复演练——真出事时手忙脚乱
十、真实案例
10.1 某电商:MySQL → TiDB 的迁移史
背景:订单库单表 8 亿行,主从延迟 30 秒,分库分表 128 库 × 16 表。
痛点:
- 跨库 JOIN 几乎不可用
- 扩容要全量迁移,耗时 72 小时
- 分布式事务(Seata)性能损失 30%
- DBA 维护 2000+ 分片
方案:
- 第 1 期:核心订单库迁 TiDB(2023)
- 第 2 期:迁移商品库(2024)
- 第 3 期:迁移用户库(2025)
收益:
- 单库扩到 30 个 TiKV 节点
- SQL 改造 5%(MySQL 协议)
- 跨表 JOIN 性能提升 10 倍
- 运维复杂度下降 50%
教训:
- 不要一次性迁全量:分库分批迁移
- 写好回滚预案:每次迁移都要有回滚方案
- 性能基线对比:迁移前后必须做压测对比
10.2 某金融:从 Oracle 到 OceanBase
背景:传统银行核心系统,Oracle RAC + 小型机。
痛点:
- Oracle 授权费高(年 1000 万+)
- RAC 扩展性差,最多 8 节点
- 国产化合规要求
方案:
- 数据库国产化:OceanBase 替代 Oracle
- 应用改造:10% SQL 调整(兼容 Oracle 语法)
- 灾备:3 地 5 中心部署
收益:
- 授权费归零
- 节点扩到 30+
- 性能持平或略优
- 通过等保 2.0
10.3 某 SaaS:MySQL → PostgreSQL
背景:CRM 系统,复杂查询多(多表 JOIN + 窗口函数)。
痛点:
- MySQL 5.7 优化器对复杂 SQL 支持差
- 一个 10 表 JOIN 查询要 30 秒
- GIS 功能缺失(附近客户查询)
方案:
- 业务层改造:用 PG 的 CTE、窗口函数重写
- 数据迁移:logical replication(MySQL → PG)
- 工具链:MyBatis 适配 PG dialect
收益:
- 复杂查询从 30 秒降到 1.5 秒
- PostGIS 替代 MongoDB(数据一致性更好)
- 报表 SQL 简化 60%
十一、总结
11.1 数据库演化的 3 大核心要素
- 业务匹配:根据数据量、QPS、一致性要求选型
- 渐进演化:单机 → 主从 → 分库分表 → 分布式,不要一步到位
- 容灾底线:3-2-1 备份铁律 + 异地灾备 + 恢复演练
11.2 何时不该上分布式数据库
| 业务特征 | 推荐方案 |
|---|---|
| 数据量 < 5000 万 | 单机 MySQL/PG |
| 数据量 5000 万 - 10 亿 | 分库分表 + 缓存 |
| 写为主(> 5000 TPS) | 分库分表优于 NewSQL |
| 强 GIS / 复杂查询 | PG 优先 |
| 强 SQL 规范 | PG 优先 |
分布式数据库的 ROI 拐点在"单表 10 亿 + 跨库 JOIN 多"。低于这个规模做了也是浪费。
11.3 常见陷阱
🛑 误区警示:这些坑别踩
- 盲目上 NewSQL:业务量没到,成本先到
- 忽视 SQL 优化:先 EXPLAIN 再谈架构
- 不验证备份:3 年没演练过
- 盲目分库分表:500 万行就分,运维成本爆炸
- 忽视 PG 优势:复杂查询死磕 MySQL
- 不监控:buffer pool 命中率、慢查询、主从延迟
- 不升级 MySQL:还在用 5.6 / 5.7
11.4 系列预告
这是 Java Web 微服务系列 的第 8 篇。后续计划:
- 服务治理:服务发现、配置中心、熔断降级
- 网关与限流:Spring Cloud Gateway、Sentinel
- 分布式事务:Seata、Saga、TCC 模式对比
- 链路追踪:SkyWalking、Jaeger
- Spring Cloud Alibaba 实战:Nacos + Sentinel + Seata 三件套
- 可观测性体系:Metrics + Logging + Tracing
- Kubernetes 落地:从 Docker 到 K8s 的完整路径
- 多活架构:单元化、流量调度、灰度发布
十二、数据库实施清单
12.1 选型层 Checklist
- 数据量评估:当前 + 3 年 + 5 年
- QPS 评估:日常 + 大促 + 业务峰值
- 一致性要求:强一致 / 最终一致
- 特性需求:GIS / JSON / 复杂查询
- 生态评估:团队熟悉度 + 招聘难度
- 成本评估:授权费 + 机器 + 运维人力
12.2 性能层 Checklist
- 索引设计:联合索引遵循最左前缀
- 慢查询治理:每月清理 top 10 慢 SQL
- EXPLAIN 检查:关键 SQL 都跑过 EXPLAIN
- 参数调优:buffer pool、work_mem、shared_buffers
- 监控告警:7 大指标全埋点
- 批量写入:避免循环单条 INSERT
- 深分页改造:游标分页替代 OFFSET
12.3 高可用层 Checklist
- 主从复制:GTID + 增强半同步
- 读写分离:ShardingSphere-JDBC
- 自动故障切换:MHA / Orchestrator
- 主从延迟监控:> 1 秒告警
- 连接数管理:池化 + 限流
- 熔断降级:Sentinel / Resilience4j
12.4 容灾层 Checklist
- 3-2-1 备份:3 份、2 种介质、1 离线
- 定期演练:每月恢复演练
- 异地备份:至少 100 公里外
- PITR 验证:时间点恢复演练
- binlog 归档:保留 14 天+
- 灾备切换 Runbook:写好且演练过
- 数据校验:备份完成后自动校验
12.5 团队层 Checklist
- DBA 能力:能讲清楚 EXPLAIN + binlog 原理
- SRE 能力:能做故障切换演练
- 研发能力:懂索引优化 + 分库分表
- 值班制度:7×24 数据库故障响应
- 复盘机制:每次故障 24 小时内复盘
十三、常见问题 FAQ
Q1:MySQL 单表 1000 万行就一定要分表吗?
A:不是。
单表 1000 万行在 InnoDB + 良好索引下毫无压力。要看的不是行数,而是:
- 实际查询响应时间(> 100ms 要优化)
- 索引深度(B+Tree 树高 > 4 才考虑)
- DDL 时间(> 1 小时考虑分表)
不要为了分表而分表。
Q2:分库分表后 JOIN 怎么办?
A:尽量避免跨库 JOIN。
替代方案:
- 业务层 JOIN:先查 A 拿 ID 列表,再查 B(接受 N+1)
- 宽表冗余:A 表冗余 B 的常用字段
- 搜索引擎:全量同步到 ES
- 数据仓库:T+1 同步到 OLAP
90% 的跨库 JOIN 可以通过业务改造绕开。
Q3:TiDB 和 OceanBase 怎么选?
A:看场景。
| 维度 | TiDB | OceanBase |
|---|---|---|
| 协议 | MySQL | MySQL / Oracle / PG |
| 一致性 | Raft | Paxos |
| HTAP | TiFlash | OB 自带 |
| 生态 | 开源、社区 | 阿里主导 + 开源 |
| Oracle 兼容 | 不支持 | 支持 |
| 适合 | 互联网、MySQL 替代 | 金融、Oracle 替代 |
MySQL 替代选 TiDB,Oracle 替代选 OceanBase。
Q4:MySQL 主从切换怎么做?
A:用 MHA / Orchestrator / MGR。
| 工具 | 特点 |
|---|---|
| MHA | 老牌,脚本方式,10 秒切换 |
| Orchestrator | Web 界面,MySQL 官方推荐 |
| MGR | MySQL 官方方案,但写性能差 |
| ProxySQL + MHA | 代理 + 自动切换 |
生产推荐 Orchestrator——可视化、自动选主、告警齐全。
Q5:xtrabackup 备份期间影响业务吗?
A:会有一些影响,但可控。
影响:
- 备份期间磁盘 IO 升高 10-20%
- 长事务的 undo log 备份会锁
- 大库(> 1TB)备份时长 4-8 小时
优化:
- 业务低峰期备份
- 用
--parallel=8并行 - 用
--throttle=200限速 - 用
--rsync减少锁
核心库建议凌晨 2-6 点备份。
Q6:PostgreSQL 的 autovacuum 怎么调?
A:分表配置。
| |
Q7:分布式数据库还要不要分库分表?
A:不要。
TiDB / OceanBase / CockroachDB 已经做了分片,再分库分表是重复建设。
- 应用层只看到一个逻辑库
- 数据库自己分片、扩缩、Rebalance
- 跨节点 JOIN / 事务由数据库自己处理
迁到 NewSQL 后,把分库分表中间件全下掉。
Q8:数据库故障时怎么快速止血?
A:预案 + 演练。
标准止血动作(5 分钟内):
- KILL 长事务:
SHOW PROCESSLIST; KILL <id>; - 切只读:
SET GLOBAL read_only = 1;(让写流量失败而非拖死) - 降级非核心业务:关掉报表、推荐、对账
- 重启主库:最后手段,先备份数据再重启
- 切主库:主库不可用时切到从库
平时就要演练——真出事时手忙脚乱 = 二次事故。
十四、推荐阅读
官方文档:
- MySQL 8.0 Reference Manual:https://dev.mysql.com/doc/refman/8.0/en/
- PostgreSQL 16 Documentation:https://www.postgresql.org/docs/16/
- TiDB Documentation:https://docs.pingcap.com/
- OceanBase Documentation:https://www.oceanbase.com/docs/
书籍:
- 《高性能 MySQL》(Baron Schwartz)—— MySQL 优化的必读圣经
- 《MySQL 8.0 高级应用与调优》(张甦)—— 国产佳作
- 《PostgreSQL 实战》(唐成)—— 中文 PG 实战
- 《数据密集型应用系统设计》(DDIA, Martin Kleppmann)—— 分布式系统圣经
- 《大型网站技术架构:核心原理与案例分析》(李智慧)—— 阿里专家作品
技术博客:
- 阿里云数据库团队博客:RDS、PolarDB、OceanBase 实战
- PingCAP 技术博客:TiDB 原理与最佳实践
- Percona Database Performance Blog:MySQL 优化全球权威
- 美团技术团队:数据库运维与分库分表
- 字节跳动技术博客:分布式数据库落地
开源项目:
- TiDB:https://github.com/pingcap/tidb
- OceanBase:https://github.com/oceanbase/oceanbase
- ShardingSphere:https://github.com/apache/shardingsphere
- Percona XtraBackup:https://github.com/percona/percona-xtrabackup
- pgcli(PG 命令行):https://github.com/dbcli/pgcli
💡 学习路径建议
- 入门:本文 + MySQL 8.0 官方文档,2 周能上手
- 进阶:阿里 / PingCAP / Percona 博客,理解工业实践
- 实战:用 ShardingSphere 搭一个分库分表 demo
- 深潜:读 TiDB 源码(建议从
tidb/server入口),理解分布式 SQL 引擎理论 + 实践 + 源码,缺一不可。
