分片键必须作为局部索引的最左前缀,否则无法触发分区裁剪;禁止更新频繁字段入索引;局部索引无法保证全局唯一性;应克制数量,优先复用组合索引。

分片键必须作为局部索引的最左前缀
局部索引在分库分表中只有和分片键强绑定,才能触发分区裁剪。如果 WHERE 条件里没带分片键,哪怕加了局部索引,MySQL 也会扫所有分片——因为每个分片的局部索引只管自己那块数据,无法跨分片联合定位。
常见错误是给高频查询字段单独建局部索引,比如订单表按 user_id 分片,却只对 order_status 建索引。结果查“所有待发货订单”时,每个分片都得全量扫描自己的局部索引,性能比单表还差。
- 正确做法:所有局部索引必须以分片键开头,例如
INDEX idx_user_status (user_id, order_status) - 若查询常带时间范围,可扩展为
(user_id, create_time, order_status),仍满足最左前缀 - 禁止出现
(order_status, user_id)这类顺序——user_id不在最左,就失去裁剪能力
避免在局部索引里包含更新频繁的字段
局部索引是每个分片独立维护的,每次更新索引列都会触发对应分片的 B+ 树分裂和页写入。如果把高变更字段(如 last_update_time、version)塞进局部索引,会显著放大写放大效应,尤其在高并发写入场景下容易拖慢整个分片。
实操建议:
- 只把真正用于过滤和排序的稳定字段放进局部索引,比如
user_id+order_type+status - 把更新频繁的字段挪到覆盖索引的尾部,或干脆不索引,用主键回表取值
- 监控
Handler_read_rnd_next和Innodb_buffer_pool_reads,飙升说明索引设计引发大量随机读
全局唯一性不能靠局部索引保证
局部索引只在单个分片内唯一,不同分片完全可能产生重复值。比如用 user_id 分片,各分片都可能有 order_no = '1001',这是设计使然,不是 bug。
所以:
- 业务上需要全局唯一的字段(如订单号、支付流水号),绝不能依赖局部索引约束
- 必须由发号器、雪花 ID 或数据库序列生成,再写入;建唯一索引也得是全局索引(但代价高,慎用)
- 如果误在局部索引上加
UNIQUE,只会导致每个分片各自校验,掩盖跨分片重复风险
局部索引数量要克制,优先复用组合索引
每个局部索引都要占用分片的内存和磁盘空间,并增加写入路径开销。分库分表后,索引膨胀是隐性性能杀手——100 个分片 × 5 个局部索引,实际就是 500 棵独立 B+ 树。
更务实的做法:
- 合并查询模式相近的条件,用一个组合索引覆盖多个
WHERE场景,比如(user_id, status, create_time)同时支持「某用户所有订单」「某用户待发货订单」「某用户最近7天订单」 - 删除长期未被
EXPLAIN命中的局部索引,用performance_schema.table_io_waits_summary_by_index_usage查使用率 - 不要为单字段
ORDER BY单独建局部索引,除非它总是和分片键一起出现











