跳转至

MySQL 深度

一句话:MySQL 优化的核心是"让查询走对索引、让数据分得开、让慢 SQL 无处藏身",是高并发链路压低 P99 延迟的关键一环。

概念

关系型数据库是大多数交易系统的存储基石。它在高并发下的瓶颈通常是:单表数据量过大导致 B+ 树层级加深与扫描变慢索引设计不合理导致全表扫描锁与隔离级别引发的性能下降

资深工程师对 MySQL 的掌握体现在三个层次:底层(B+ 树为什么、聚簇索引与非聚簇索引、回表与覆盖索引)、设计(分库分表如何选分片键、跨分片难题如何解)、治理(慢 SQL 如何发现与系统性治理,把核心接口 P99 降下来)。报告量化"核心接口 P99 延迟下降 50%"正是这条链路的典型成果。

原理

InnoDB 索引:B+ 树

InnoDB 的索引数据结构是 B+ 树。理解 B+ 树要抓住三个特征:

  1. 非叶子节点只存索引(键),不存数据:一个节点能放很多键,树更矮,磁盘 IO 次数少。
  2. 所有数据都存放在叶子节点,且叶子节点之间用双向链表相连:范围查询只需定位起点,沿链表顺序扫描即可。
  3. 三层 B+ 树可支撑约千万级行:根节点常驻内存,每次查询约 3 次磁盘 IO。

为什么是 B+ 树而不是 B 树/红黑树/Hash

结构 劣势(为何不用)
Hash 不支持范围查询、排序,O(1) 但场景受限
红黑树/二叉树 树高随数据量线性增长,磁盘 IO 太多
B 树 非叶子节点也存数据,扇出小、树更高;范围查询需中序遍历回溯
B+ 树 扇出大、树矮(3 层千万级)、叶子链表支持高效范围扫描

聚簇索引与回表

InnoDB 的聚簇索引:数据行本身按主键顺序存储在 B+ 树叶子节点,主键索引即数据。二级索引(非聚簇)叶子节点存的是主键值,查到主键后还需回表到聚簇索引取完整行。

覆盖索引:查询的列全部被某个索引覆盖,无需回表,EXPLAIN 显示 Extra: Using index。这是优化高频查询的利器。

执行计划 EXPLAIN

字段 关注点
type 访问类型,const > eq_ref > ref > range > index > ALLALL 是全表扫描(红旗)
key 实际使用的索引;NULL 表示没用索引
rows 预估扫描行数,越小越好
Extra Using index(覆盖索引,好)、Using filesort(额外排序,警惕)、Using temporary(临时表,警惕)

分库分表(ShardingSphere)

单表数据量达到千万级、单库 QPS 触顶时,需要水平拆分。ShardingSphere 是主流的 Java 分库分表中间件(Sharding-JDBC 嵌入式、Sharding-Proxy 独立代理)。

分片键选择是核心决策。报告场景采用user_id 水平拆分,因为:

  • 交易链路绝大多数查询都带 user_id(查订单、查账户),按 user_id 分片能让"同一用户的数据落在同一分片",避免跨分片。
  • user_id 离散度高、分布均匀,数据不会倾斜。

分片策略通常配合分库(分实例)+ 分表(同实例多表):先按 user_id 取模分到 N 个库,再在每个库内取模分到 M 张表。

跨分片难题

分库分表带来三个典型难题:

难题 成因 解法
跨分片 JOIN 数据在不同库,无法直接 JOIN 应用层组装、冗余字段、绑定表(ShardingSphere binding table)
跨分片分页 LIMIT offset, size 需要每个分片取 offset+size 再归并,深度分页性能差 游标分页/带上一页末尾 ID、二次查询、ES 宽表
全局唯一 ID 各分片自增会冲突 雪花算法(Snowflake)、号段模式(Leaf)

慢 SQL 治理与 P99

慢 SQL 是 P99 劣化的主因之一。治理闭环:

  1. 采集:开启 MySQL 慢查询日志(long_query_time)+ APM(SkyWalking/Pinpoint)抓接口 SQL RT。
  2. 分析EXPLAIN 看执行计划,识别全表扫描、未命中索引、回表过多、filesort。
  3. 优化:加合适的联合索引(最左前缀)、用覆盖索引消除回表、改写 SQL(避免 SELECT *、避免索引列上函数运算)。
  4. 验证:压测对比 P99,确认有效。

实战要点

结合报告"分库分表(ShardingSphere 按 user_id)+ 慢 SQL 治理,核心接口 P99 下降 50%"的经验:

  1. 分片键一定要贴合主查询路径:选 user_id 是因为 90% 以上的交易查询都带它;若选了 order_id,则"查某用户所有订单"必跨分片。分片键选错是分库分表最大的设计债,后期几乎无法回退。

  2. 联合索引遵守最左前缀与区分度(user_id, create_time) 而不是 (create_time, user_id);高区分度列放前面。避免在索引列上做函数(WHERE DATE(create_time)=...)会失效,改范围查询。

  3. 深度分页用游标LIMIT 100000, 20 仍要扫描前 100020 行。改 WHERE id > last_id ORDER BY id LIMIT 20(游标分页),P99 可从数百毫秒降到个位数毫秒。

  4. 建立慢 SQL 监控与治理闭环:APM 自动抓取 Top10 慢 SQL,每周治理,把单条 SQL RT 压到阈值内,是 P99 持续下降的系统性工程,而非一次性优化。

  5. 分库分表不是银弹,先优化再拆:单表能扛就不要拆。拆之前先穷尽索引优化、读写分离、归档冷数据。一旦拆了,跨分片统计、JOIN、事务(变成分布式)都会带来复杂度。

  6. 跨分片统计用 ES 宽表/离线数仓:实时大盘统计不要在分片 MySQL 上做(要查全部分片再聚合),落到 ES 或离线 Hive。

本节相关题目

难度 题目 链接
基础 InnoDB 索引数据结构、为何 B+ 树 → 题库
进阶 分库分表后跨分片 JOIN 与分页 → 题库
深度 不停机分片扩容迁移方案 → 题库
深度 "核心接口 P99 -50%"统计口径与归因 → 题库