先唠两句
大家好啊,我是阿垚。
欢迎来到《100个“反常识”经验》第09期。
上期聊了被DDoS后的48小时应急。今天聊一个数据库方向的老大难问题:
一条慢查询拖垮整个库。
你有没有遇到过?数据库CPU突然飙到100%,连接数爆满,业务大面积超时。排查后发现,罪魁祸首只是一条看似简单的SQL。
这不是数据库的错,是你对索引和查询计划的理解还不够深。
一次让人后背发凉的故障
周四下午,业务高峰期。数据库报警:CPU使用率持续100%。
登录数据库,查看当前正在执行的查询:
sql
SHOW PROCESSLIST;
发现大量查询处于“Sending data”状态,而且都在执行同一张订单表的查询:
sql
SELECT * FROM orders WHERE user_id = 12345 AND status = 1 ORDER BY created_at DESC;
这张表有2000万行数据。user_id和status字段都没有索引。每次查询都要全表扫描,排序更是噩梦。
这条SQL是刚上线的一个新功能触发的,QPS不算高(只有几十),但每次查询耗时2-3秒,很快就把数据库的连接池和CPU打满了。
慢查询为什么会拖垮整个库?
一条慢查询看似只影响自己,但实际上会引发连锁反应:
占用数据库连接,导致其他正常请求排队等待
消耗大量CPU和IO,拖慢所有查询
引发连接风暴,应用端不断重试,进一步加剧压力
可能导致主从延迟,影响读写分离架构
排查三步法
第一步:快速定位慢查询
sql
-- 查看当前正在执行的慢查询 SHOW FULL PROCESSLIST; -- 查看慢查询日志(需提前开启) SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;
第二步:分析查询执行计划
sql
EXPLAIN SELECT * FROM orders WHERE user_id = 12345 AND status = 1 ORDER BY created_at DESC;
关注几个关键字段:
type:ALL 表示全表扫描(危险)rows:扫描的行数,越大越慢Extra:Using filesort 表示需要额外排序
第三步:验证索引效果
sql
-- 创建复合索引 CREATE INDEX idx_user_status_time ON orders (user_id, status, created_at); -- 再次执行EXPLAIN,观察type和rows变化
永久防范方案
✅建立索引规范
WHERE条件中的字段必须建索引
高频查询的排序字段应包含在复合索引中
避免在索引列上使用函数或计算
✅开启慢查询监控
sql
-- 慢查询阈值设为1秒 SET GLOBAL long_query_time = 1; -- 记录未使用索引的查询 SET GLOBAL log_queries_not_using_indexes = ON;
✅使用查询限流
应用层:对高风险接口进行限流和熔断
数据库层:使用pt-query-digest定期分析
✅读写分离 + 分库分表
将统计类、报表类查询分流到从库
超千万级大表考虑按时间或业务键分表
一键慢查询分析脚本
bash
#!/bin/bash # 分析最近1小时的慢查询日志 pt-query-digest --since '1h' /var/log/mysql/slow.log > slow_report.txt
下期预告
《100个“反常识”经验10:主从延迟从10秒飙到10小时》
评论区分享你遇到过的印象最深的慢查询案例。
——阿垚,一个踩过2万次坑的“老”IT