9.MySQL 性能分析与优化

第一板块:性能分析与瓶颈诊断(先诊断)

在对一个未知数据库或慢 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 rowsSending dataSorting 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 访问/连接类型(最核心性能指标 NULLsystemconsteq_refrefrangeindexALL
possible_keys 可能用到的索引 显示该查询可能使用的索引列表。
key 实际使用的索引 如果为 NULL,表示没有使用任何索引(全表扫描/索引失效)。
key_len 实际使用索引的字节数 表示索引中使用的字节数,不损失精确性前提下越短越好。
rows 预估扫描行数 估算必须要读取扫描的行数,rows 越小越好。
filtered 返回行数占读取行数的百分比 表示返回结果的行数占需要读取行数的百分比,越大越好(100% 最佳)。
Extra 额外补充信息 Using index(覆盖索引好)、Using filesort(内存/磁盘排序坏)、Using temporary(临时表坏)。
2. type 访问类型 8 大级别由好到差

NULLsystemconsteq_refrefrangeindexALL(生产环境要求达到 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. 主键设计四大避坑原则
  1. 降低主键长度(二级索引节点存主键,主键越短 B+ 树扇出越高)。
  2. 主键顺序插入 (推荐 AUTO_INCREMENT)。
  3. 避免使用 UUID 或乱序自然主键
  4. 避免修改主键(引发整行数据删除与重插入物理开销)。

七、 排序优化: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 优化三大工程避坑法则
  1. UPDATE 的 WHERE 条件字段必须建立索引。
  2. 业务代码层尽量先查出主键 id,再根据 WHERE id = ? 进行精准更新(锁粒度最小)。
  3. 严防隐式类型转换等导致的索引失效降级全表锁。

第三板块:全局总结与心法速记

一、 性能分析四大诊断工具对比

诊断工具 / 指令 作用与核心目的 配置文件 / 生效方式 适用阶段
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 紧扣索引防表锁!"

相关推荐
思考着亮36 分钟前
10.MySQL 锁机制
后端
我的div丢了肿么办40 分钟前
go语言中基本数据类型的转换
后端·go
XuCoder42 分钟前
你写的每条 SQL 都没加过锁,可 MySQL 凭什么不怕两个事务打架?
数据库·后端
尼古拉斯-托尔斯泰-赵四1 小时前
Go 语言,你需要了解的一些规则
开发语言·后端·golang
程序员贺加贝1 小时前
列表导出不够用-SaaS-ERP-单据详情导出的-Provider-模板与文档型-Excel-设计
java·后端·设计模式·架构·excel
SimonKing1 小时前
写文档的最佳搭档:Typora+PicList+SM.MS
java·后端·程序员
choumou_M1 小时前
MySQL_1:数据库悲观锁
后端·spring·spring cloud
Lost of 程序猿2 小时前
ASP.NET Core SignalR 实战:从实时推送到底层协议与高可用部署
后端·asp.net