第一板块:性能分析与瓶颈诊断(先诊断)
在对一个未知数据库或慢 SQL 进行性能调优时,首要任务不是盲目改写 SQL 或加索引,而是通过专业的诊断工具掌握数据库负载类型、锁定作案现场并抓取索引扫描细节。
一、 诊断第一步:SQL 执行频率分析(定位读写倾斜)
在进行性能调优时,首先需要掌握当前数据库的业务负载类型(到底是读密集型、还是写密集型)。
1. 核心诊断指令:SHOW GLOBAL STATUS
通过 SHOW [SESSION|GLOBAL] STATUS 命令可以查看服务器状态变量。其中以 Com_ 开头的变量记录了各种 SQL 语句的执行频次。
sql
-- 查看当前数据库 INSERT、UPDATE、DELETE、SELECT 的累积访问频次(7个下划线通配符)
SHOW GLOBAL STATUS LIKE 'Com_______';
2. 核心状态变量解读
| 状态变量名称 | 含义说明 | 调优决策导向 |
|---|---|---|
Com_select |
累计执行 SELECT 查询语句的次数 |
占 80% 以上 ➔ 读密集型业务,重点优化索引、覆盖索引与缓存 |
Com_insert |
累计执行 INSERT 插入语句的次数 |
占比较高 ➔ 写密集型业务,重点优化批量插入、自增主键与控制索引数量 |
Com_update |
累计执行 UPDATE 更新语句的次数 |
频繁更新 ➔ 检查更新字段是否带索引,防全表锁与死锁 |
Com_delete |
累计执行 DELETE 删除语句的次数 |
频繁删除 ➔ 改用逻辑删除(is_deleted)或定时分批清理 |
3. 底层工程决策与优化方向
sql
┌── 读多写少(Com_select 主导) ➔ 建立 B+ 树索引、覆盖索引、主从读写分离、Redis 热点缓存
【SQL 性能调优决策树】 ──┤
└── 写多读少(Com_insert/update)➔ 减少不必要的二级索引、批量事务提交、分库分表
- 场景 A:读密集型(
Com_select占绝对主导)- 数据库的瓶颈主要在磁盘随机 I/O 与 B+ 树下潜;
- 优化方案:为高频查询列建立合适的单列/联合索引,尽量凑成"覆盖索引"免去回表;对于超高频热点数据,引入 Redis 缓存减轻数据库压库。
- 场景 B:写密集型(
Com_insert/Com_update占绝对主导)- 每次写入/更新都需要同步维护该表上的所有二级索引 B+ 树,二级索引越多,写入性能越差;
- 优化方案:精简无用索引;批量写入采用事务包裹;使用自增主键保持 B+ 树顺序追加,避免频繁页分裂。
二、 诊断第二步:慢查询日志(Slow Query Log)精准定位慢 SQL
当确定系统属于"读多写少"后,需要找出究竟是哪几条 SQL 执行时间过长拖垮了系统。慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认 10 秒)的 SQL 语句日志。
1. 开启与配置慢查询日志(配置文件 vs 命令行双模式)
方法一:修改配置文件永久生效(生产环境强烈推荐)
编辑 Linux 主配置文件 /etc/my.cnf (或 /etc/mysql/my.cnf),在 [mysqld] 标签下追加配置:
ini
[mysqld]
# 开启慢日志查询开关(1 为开启,0 为关闭)
slow_query_log=1
# 设置慢日志的阈值时间为 2 秒
long_query_time=2
方法二:动态命令行修改临时生效(无需重启服务)
sql
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 2;
三、 诊断第三步:SHOW PROFILES 细节剖析(耗时与资源开销)
SHOW PROFILE 能够在 SQL 执行完毕后,精确展示该 SQL 在每一个阶段(如 Searching rows、Sending data、Sorting result)的微秒级耗时与 CPU/磁盘 I/O 资源占用。
sql
-- 查看当前 MySQL 是否支持 profile 分析
SELECT @@have_profiling;
-- 开启 profile 分析(会话级别)
SET profiling = 1;
-- 查看最近执行的 SQL 列表及其总耗时 query_id
SHOW PROFILES;
-- 查看指定 query_id 的详细阶段耗时(例如 CPU 与 I/O 耗时)
SHOW PROFILE CPU, BLOCK IO FOR QUERY 10; -- 10 代表 SHOW PROFILES 结果列表中查到的指定 query_id
四、 诊断第四步:EXPLAIN 执行计划各字段与 type 访问级别深度剖析
EXPLAIN 是 MySQL 优化器最核心的诊断报告。在任何 SELECT 语句前加上 EXPLAIN 关键字,MySQL 会输出该 SQL 的执行计划及索引命中细节。
1. EXPLAIN 执行计划 9 大字段含义详解
| 字段名称 | 物理含义说明 | 工程优化解读与诊断原则 |
|---|---|---|
id |
SELECT 查询的序列号 |
标识执行顺序:id 相同从上到下执行;id 不同值越大越先执行。 |
select_type |
SELECT 的查询类型 |
SIMPLE(简单表)、PRIMARY(主查询)、SUBQUERY(子查询)等。 |
type |
访问/连接类型(最核心性能指标) | NULL ➔ system ➔ const ➔ eq_ref ➔ ref ➔ range ➔ index ➔ ALL |
possible_keys |
可能用到的索引 | 显示该查询可能使用的索引列表。 |
key |
实际使用的索引 | 如果为 NULL,表示没有使用任何索引(全表扫描/索引失效)。 |
key_len |
实际使用索引的字节数 | 表示索引中使用的字节数,不损失精确性前提下越短越好。 |
rows |
预估扫描行数 | 估算必须要读取扫描的行数,rows 越小越好。 |
filtered |
返回行数占读取行数的百分比 | 表示返回结果的行数占需要读取行数的百分比,越大越好(100% 最佳)。 |
Extra |
额外补充信息 | Using index(覆盖索引好)、Using filesort(内存/磁盘排序坏)、Using temporary(临时表坏)。 |
2. type 访问类型 8 大级别由好到差
NULL ➔ system ➔ const ➔ eq_ref ➔ ref ➔ range ➔ index ➔ ALL(生产环境要求达到 range 级以上,绝对防止 ALL!)。
第二板块:SQL 场景针对性调优(后手术)
完成瓶颈定位后,根据具体 SQL 语句的业务场景,实施针对性的物理存储与逻辑语法优化。
五、 写入优化:INSERT 与大批量数据加载(LOAD DATA)
1. 常规 INSERT 写入的三大优化手段
- 批量插入 :合并多条为一条
INSERT INTO table VALUES (...), (...);(控制在 500 ~ 1000 条/次)。 - 手动提交事务 :显式
START TRANSACTION;包裹多条插入后统一COMMIT;,减少磁盘刷盘开销。 - 主键顺序插入 :推荐
AUTO_INCREMENT自增主键,最右侧数据页顺序追加,零物理页分裂。
2. 百万级天量数据加载:LOAD DATA LOCAL INFILE(极速导入)
sql
SET GLOBAL local_infile = 1;
LOAD DATA LOCAL INFILE '/root/sql1.log' INTO TABLE tb_user FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';
六、 主键优化:InnoDB 索引组织表 (IOT) 与物理页管理
1. 索引组织表(Index Organized Table - IOT)
InnoDB 表数据都是根据主键物理顺序组织存放的。物理逻辑结构五级:表空间 ➔ 段 ➔ 区 (1M/64页) ➔ 页 (16K) ➔ 行。
2. 物理页分裂与页合并机制
- 页分裂:主键乱序插入导致满页被迫切分并申请新页,产生物理碎片并浪费 50% 物理空间。
- 页合并 :删除记录仅标记(flagged),当删除达到
MERGE_THRESHOLD(默认 50%) 时与相邻页合并。
3. 主键设计四大避坑原则
- 降低主键长度(二级索引节点存主键,主键越短 B+ 树扇出越高)。
- 主键顺序插入 (推荐
AUTO_INCREMENT)。 - 避免使用 UUID 或乱序自然主键。
- 避免修改主键(引发整行数据删除与重插入物理开销)。
七、 排序优化:ORDER BY 深度调优(Using filesort vs Using index)
1. 物理排序机制对比
Using filesort:无法利用索引顺序,在sort buffer中进行外部排序(低效,数据超大溢出到磁盘)。Using index:通过有序索引节点直接返回结果(零额外排序,极致高效)。
2. 混合排序优化实战
sql
-- 一升一降混合排序(ORDER BY age ASC, phone DESC),显式创建带方向的联合索引
CREATE INDEX idx_user_age_phone_ad ON tb_user(age ASC, phone DESC);
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age ASC, phone DESC;
八、 分组优化:GROUP BY 深度调优(Using temporary 破解与最左前缀)
1. 物理原理与防临时表法则
无索引的 GROUP BY 会触发 Using temporary; Using filesort 内存临时表排序,性能极其低下。
- 法则一:分组字段建立合适的复合索引,彻底干掉临时表。
- 法则二:严格遵循复合索引的最左前缀法则。
- 法则三 :结合
WHERE等值条件补全最左前缀。
sql
-- 配合 WHERE 过滤条件补全最左前缀(WHERE 提供了最左列 profession)
EXPLAIN SELECT age, COUNT(*) FROM tb_user WHERE profession = '软件工程' GROUP BY age;
九、 分页优化:LIMIT 深分页调优(覆盖索引 + 延迟关联)
1. 深分页 LIMIT 2000000, 10 物理崩塌成因
MySQL 必须先排序并读取前 2000010 行整行完整数据(Data),最后抛弃前 2000000 行返回 10 行,产生了 2000010 次随机磁盘 I/O 和回表开销!
2. 优化方案:覆盖索引 + 子查询(延迟关联 Deferred Join)
sql
-- 延迟关联 Deferred Join 高效写法:在覆盖索引树上只扫描主键 id,仅定位最后 10 个 id 后再 JOIN 原表
SELECT u.*
FROM tb_user u
JOIN (
SELECT id FROM tb_user ORDER BY id LIMIT 2000000, 10
) AS temp ON u.id = temp.id;
3. 游标分页 / 记号分页(Seek 法)
sql
-- 游标分页(时间复杂度由 O(N) 降为 O(1),直接做 B+ 树 range 范围下潜定位)
SELECT * FROM tb_user WHERE id > 2000000 ORDER BY id LIMIT 10;
十、 统计优化:COUNT(*) 深度调优(4 种 COUNT 物理机制与性能对比)
1. 4 种 COUNT 的底层物理传输与处理机制
COUNT(*):InnoDB 不读取具体字段值,且自动挑选体积最小的二级索引树遍历,性能最高。COUNT(1):不读取字段值,硬编码常量数字1累加,性能与COUNT(*)几乎持平。COUNT(主键):提取主键id值传输给 Server 层按行累加,性能中等。COUNT(字段):提取整行该字段值,Server 层逐行校验NULL值,性能最差。
2. 性能效率排行榜
text
【效率排序】 count(字段) < count(主键 id) < count(1) ≈ count(*) ➔ 结论:官方推荐尽量使用 count(*)
十一、 更新优化:UPDATE 深度调优(防行锁升级为表锁)
1. 物理本质与锁升级灾难
【红字铁律】:InnoDB 的行锁是针对索引加的锁,不是针对记录行加的锁!并且更新条件所使用的索引绝对不能失效,否则会直接从行锁升级为表锁!
sql
-- 场景 A:根据主键 id 更新(带索引 ➔ 精准加行锁 Record Lock)
UPDATE student SET no = '2000100100' WHERE id = 1;
-- 场景 B:根据无索引字段更新(无索引/索引失效 ➔ 行锁暴退升级为表锁 Table Lock!)
UPDATE student SET no = '2000100105' WHERE name = '韦一笑';
-- 灾难后果:给整张 student 表加表锁,当前事务提交前,其他事务对该表的所有写操作全线挂起堵塞死锁!
2. UPDATE 优化三大工程避坑法则
- UPDATE 的
WHERE条件字段必须建立索引。 - 业务代码层尽量先查出主键
id,再根据WHERE id = ?进行精准更新(锁粒度最小)。 - 严防隐式类型转换等导致的索引失效降级全表锁。
第三板块:全局总结与心法速记
一、 性能分析四大诊断工具对比
| 诊断工具 / 指令 | 作用与核心目的 | 配置文件 / 生效方式 | 适用阶段 |
|---|---|---|---|
SHOW GLOBAL STATUS LIKE 'Com_______' |
统计整体读写比例(Com_select vs Com_insert) |
内存实时变量,随时直接查询 | 宏观战略评估(定优化方向) |
slow_query_log (慢查询日志) |
捕获执行时间超过阈值(如 > 2s)的慢 SQL | 修改 /etc/my.cnf (slow_query_log=1) |
微观瓶颈定位(抓作案现场) |
SHOW PROFILE |
剖析单条 SQL 在各个生命周期阶段的 CPU/IO 耗时 | 命令行 SET profiling = 1; 动态开启 |
执行阶段精细排查 |
EXPLAIN |
分析 SQL 的索引命中情况与扫描行数(type 级别) |
SQL 前加 EXPLAIN 关键字直接执行 |
SQL 结构与索引优化 |
二、 【终极一句话速记】
"调优先查 Com 频率(定性读写),慢日志抓作案现场,EXPLAIN 看 type 避 ALL;自增主键顺序追加防页裂,ORDER BY 复合索引控同向,GROUP BY 最左前缀干掉临时表,LIMIT 深分页延迟关联,COUNT 统计首选 COUNT(*),UPDATE 紧扣索引防表锁!"