分库分表、复制与读写分离
大表先优化,再拆库
拆分会引入分布式事务和跨库 JOIN,不是第一选择。单表还能撑住时,按这条路径收紧:
- 限制扫描范围,禁止无 WHERE 的大查询
- 索引、字段类型、冷热列拆分
- 缓存热点,汇总表抗统计
- 读写分离分担读
- 垂直拆:列太多或大字段拖累整行
- 水平拆:行太多,单表到千万、上亿还在恶化
垂直拆和水平拆
垂直分表:一张宽表按列拆。常用字段一张,大字段、低频字段另一张,减少 IO 和锁竞争。
垂直分库:按业务拆到不同库甚至不同机器,连接数和 CPU 隔离,例如订单库、用户库。垂直拆完,单表行数仍可能很大,主键会冗余,跨表事务变难。
水平分表:同一库内按规则把行拆到 order_0、order_1,解决单表过大。
水平分库:行拆到不同库或机器,解决单机瓶颈;一个库挂了不至于全站写挂。
水平拆的代价是跨分片 JOIN、分布式事务、扩容迁移、全局 ID。路由键选错会把复杂度白付:只按 user_id 分片,运营却总按时间扫全部分片,等于每条统计都打所有库。
经验顺序:先按业务垂直拆,压力仍大再上缓存和读写分离,数据量继续涨再水平拆。
分片后的全局 ID
每个分片不能各自从 1 自增,需要全局唯一、尽量趋势递增的主键。
- UUID:本地生成、无中心。太长且乱序,不适合当 InnoDB 主键
- 号段表:单独一张发号表每次取一段,实现简单;中心库会成为瓶颈,要批量拿号缓解
- Redis INCR:快,要保证持久化和高可用,避免单点丢号
- 雪花 Snowflake:64 bit = 1 位符号 + 41 位毫秒时间 + 10 位机器 + 12 位序列。趋势递增、本地生成。要处理时钟回拨和 workerId 唯一
- 美团 Leaf:号段模式 + 雪花模式,生产里较常见
主键用 BIGINT 雪花或号段,业务单号另存。不要把 36 位 UUID 字符串当聚簇索引。
主从复制
主库写 binlog,从库重放,数据跟上主库。
- 主库提交事务时写 binlog
- 主库 dump 线程把 binlog 发给从库
- 从库 IO 线程写成 relay log
- 从库 SQL 线程(8.0 常是协调线程 + 多个 worker)重放 relay log
用途包括读扩展、高可用切换、备份、在从库做在线 DDL 或升级验证。
默认复制是异步的:主库提交成功,不代表从库已经放完,会有延迟。半同步要求至少一台从库收到 binlog 才给客户端成功,降低丢数据窗口。GTID 给每个事务全局编号,failover 更清晰。binlog 格式里 ROW 最安全,STATEMENT 可能主从不一致,MIXED 居中。
读写分离
读走从库,写走主库,建立在复制之上。读摊到多台后,主库锁竞争下降;从库挂了还可以留一台只读。
复制延迟是主要代价:刚写入立刻读从库,可能读不到。提交后读、强一致读要走主库,或等 GTID 追上。中间件或业务自己要能识别「写完马上读」。
从库同样建议 InnoDB,不要为了「从库用 MyISAM 更快」回退引擎。从库太多,主库 dump 也会吃资源。
读写分离解决的是读流量,不解决单表过大。表过大还是要分库分表。