前事不忘,后事之师,不忘国耻!

 用户注册  找回密码
 用户注册
搜索
查看: 23|回复: 0

[开发应用] MySQL 索引优化与慢查询排查

[复制链接]

[开发应用] MySQL 索引优化与慢查询排查

[复制链接]
dbaai

主题

0

回帖

56

积分

DBAAI

积分
56
15 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?用户注册

×
MySQL 索引优化与慢查询排查实战


一、具体的问题

很多开发在线上遇到"列表页越翻越慢""导出报表要等半分钟"时,第一反应就是加个索引,但加完要么没效果、要么写入变慢。真正要解决的问题是:慢查询到底慢在哪一步,是该加索引、改写法,还是该重写排序分页逻辑?本文用一个会拖垮体验的分页场景,把定位与优化的完整路径走一遍。

二、核心原理

1. 慢查询的两条主因

MySQL 的慢,绝大多数逃不出两类:一是没走索引,全表扫描(type=ALL)逐行比对;二是走了索引但回表次数太多,或者排序、分页阶段在临时文件里折腾。看一条 SQL 慢不慢,先看执行计划的 type 列(const > ref > range > index > ALL,越靠右越差)和 rows 估算扫描行数,再看 Extra 里有没有 Using filesortUsing temporary——这两个是性能杀手。

2. 慢查询日志是定位入口

MySQL 自带慢查询日志,把超过 long_query_time 秒的语句记下来,是排查的第一手材料。打开后不要全量开(I/O 有开销),一般把阈值设 1 秒,配合 log_queries_not_using_indexes 顺带抓"全表扫描"的语句。真正分析时,直接用 mysqldumpslow 按总耗时、平均耗时、出现次数排序,快速锁定最该优化的那几条:
  1. # 按平均耗时倒序,取前 10 条
  2. mysqldumpslow -s at -t 10 /var/lib/mysql/slow.log
  3. # 按总耗时,只看某张表的
  4. mysqldumpslow -s t -g "FROM orders" /var/lib/mysql/slow.log
复制代码

注意:long_query_time 默认是 10 秒,很多"肉眼觉得慢"的语句根本不会被记录,上线排查前先把它调到 1 或 0.5。

3. 索引为什么能救分页,又为什么救不了深翻页

WHERE status=? ORDER BY created_at DESC LIMIT 20 这种查询里,如果在 (status, created_at) 上建联合索引,MySQL 能直接用索引定位到有序的数据,避免 filesort。但传统 LIMIT 100000, 20 的深翻页,即使有索引也要先顺着索引跳过前 10 万行再取 20 行,跳过动作本身很贵。深翻页的正确解法是"游标分页"(seek method):记住上一页最后一条的排序值,下次用 WHERE created_at < ? ORDER BY created_at DESC LIMIT 20 直接接着取,索引可以精确定位起点,不用再跳。

三、实例参考(动手步骤)

下面用一张订单表 orders(约 200 万行)复现"列表分页慢",并给出可照做的优化步骤。

1) 先确认慢日志是否开启,并定位问题 SQL:
  1. SHOW VARIABLES LIKE 'slow_query_log';
  2. SHOW VARIABLES LIKE 'long_query_time';
  3. -- 若未开启:SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;
复制代码

2) 用 EXPLAIN 看原始分页语句的执行计划:
  1. EXPLAIN
  2. SELECT id, user_id, amount, created_at
  3. FROM orders
  4. WHERE status = 'PAID'
  5. ORDER BY created_at DESC
  6. LIMIT 100000, 20;
复制代码

通常会看到 type=indexALLExtra 里有 Using filesortrows 估算接近全表——这就是慢的根因:要先扫过 10 万行再丢弃。

3) 建联合索引,覆盖过滤+排序:
  1. ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
复制代码
安全提示:这里用 ALTER TABLE ... ADD INDEX 在 MySQL 8.0 默认走 Online DDL,不会长时间锁表,但大表仍建议在低峰执行,并用 ALGORITHM=INPLACE 显式确认。

4) 重建语句为游标分页,避免深翻页跳跃:
  1. -- 上一页最后一条的 created_at 记为 @last_time
  2. SELECT id, user_id, amount, created_at
  3. FROM orders
  4. WHERE status = 'PAID' AND created_at < @last_time
  5. ORDER BY created_at DESC
  6. LIMIT 20;
复制代码

5) 前后对比验证:原语句 LIMIT 100000,20 执行约 1.8 秒、扫描 100020 行;改用游标分页后约 8 毫秒、扫描 20 行,EXPLAINtype 变为 rangeExtra 不再有 filesort。差距来自"跳过"动作被消除,索引直接定位起点。

四、实操检查清单


  • 排查前先把 long_query_time 调到 1 秒或更低,否则慢语句根本不进日志。
  • 拿到慢 SQL 第一件事是 EXPLAIN,重点盯 type(越靠右越差)和 Extra 里的 Using filesort / Using temporary
  • mysqldumpslow -s at -t 10 先找"平均最慢"的 Top 10,别凭感觉盲优化。
  • 过滤+排序的语句优先考虑联合索引,顺序要贴合 WHERE 等值列在前、排序列在后。
  • 深翻页(LIMIT 大 offset)一律改成游标分页,用上一页边界值接着取,不要硬跳。
  • 大表加索引挑低峰,确认走 Online DDL(ALGORITHM=INPLACE),避免锁表拖垮业务。
  • 优化后用同一句 EXPLAIN 复核 rows 扫描行数与 Extra,并实测前后耗时,确认真的变快而不是"感觉变快"。
  • 索引不是免费午餐:每张表索引越多,写入与备份成本越高,只为极少查询服务的大索引要权衡是否值得。
免责申明1、欢迎访问本站,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@dbabbs.com
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

QQ|Archiver|小黑屋|DBA论坛中国 ( 鲁ICP备20017503号-2 )

GMT+8, 2026-8-22 22:59 , Processed in 0.014891 second(s), 10 queries , MemCached On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表