慢查询批量优化脚本解决数据库卡顿查询缓慢
数据库出现卡顿、页面加载变慢,背后往往藏着大量慢查询。
慢查询批量优化脚本能帮你自动收集耗时 SQL、定位低效语句并给出优化方向,避免逐个手动排查。
本文会带你从开启慢查询日志开始,写一个简单的批量分析脚本,完成一轮可验证的优化,整个过程不需要很深的基础,跟着命令敲就能跑起来。
先把慢查询日志打开
没有日志就等于没有体检报告。
以 MySQL 为例,先确认当前是否开启了慢查询记录:
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
如果 slow_query_log 是 OFF,执行下面的 SQL 开启。
这里同时把阈值设为 1 秒,意思是执行时间超过 1 秒的 SQL 会被记录:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
注意:修改 long_query_time 后,新连接才生效,建议直接在数据库配置文件 my.cnf(Linux)或 my.ini(Windows)里加上:
slow_query_log = 1
long_query_time = 1
slow_query_log_file = /var/log/mysql/slow.log
改完重启 MySQL 服务。
确认日志文件已经生成后,先跑一段时间(建议至少 15 分钟),积累足够样本再分析。
写一个批量慢查询分析脚本
拿到慢查询日志后,可以用脚本把耗时最高的 SQL 批量提取出来。
下面是一个 Python 脚本思路,适合日志格式为 # Time 和 # Query_time 的 MySQL 慢日志:
import re
from collections import defaultdict
log_path = "/var/log/mysql/slow.log"
sql_stats = defaultdict(lambda: {"count": 0, "max_time": 0, "sql": ""})
with open(log_path, "r", encoding="utf-8", errors="ignore") as f:
lines = f.readlines()
current_sql = []
exec_time = 0
for line in lines:
if line.startswith("# Query_time:"):
exec_time = float(line.split(":")[1].split()[0])
elif line.strip() and not line.startswith("#"):
current_sql.append(line.strip())
elif line.startswith("#"):
if current_sql:
sql_text = " ".join(current_sql)[:200]
sql_stats[sql_text]["count"] += 1
sql_stats[sql_text]["max_time"] = max(sql_stats[sql_text]["max_time"], exec_time)
sql_stats[sql_text]["sql"] = sql_text
current_sql = []
# 按最大耗时排序,输出 TOP 10
sorted_sql = sorted(sql_stats.items(), key=lambda x: x[1]["max_time"], reverse=True)[:10]
for sql, info in sorted_sql:
print(f"耗时: {info['max_time']}s | 次数: {info['count']}")
print(f"SQL: {info['sql']}\n")
把脚本保存为 analyze_slow.py,运行:
python3 analyze_slow.py
脚本会打印出最耗时的 10 条 SQL。
对于临时救急,这个结果已经够用了。
如果想更全面,可以直接用 Percona Toolkit 里的 pt-query-digest:
pt-query-digest /var/log/mysql/slow.log
它能按平均耗时、总耗时、次数排序,并给出每一类 SQL 的样本和 EXPLAIN 建议,适合日志量较大的场景。
从分析报告里找出卡顿真凶
拿到 TOP SQL 后,先判断问题类型,再决定优化动作。
常见的有三类:
- 全表扫描:
EXPLAIN结果里type是ALL,说明没用上索引。优先看WHERE条件字段是否缺索引。 - 索引失效:字段上用了函数、隐式类型转换,或者前导通配符
LIKE '%xx',都会导致索引失效。 - 单次查询取数过多:
LIMIT太大或没有LIMIT,一次拉几十万行,数据库慢且网络传输也慢。
对确认需要加索引的语句,执行:
ALTER TABLE 表名 ADD INDEX idx_字段名 (字段名);
比如慢 SQL 里经常出现 WHERE user_id = 100 ORDER BY create_time DESC,可以建联合索引:
ALTER TABLE orders ADD INDEX idx_user_time (user_id, create_time);
不要一次性批量加很多索引,因为索引不是越多越好,写操作会变慢。
建议每次只加 1-2 个,观察后再继续。
执行优化后的效果验证
优化完不能只看日志里没有了,要量化前后变化。
先用 EXPLAIN 确认执行计划已经改变:
EXPLAIN SELECT * FROM orders WHERE user_id = 100 ORDER BY create_time DESC LIMIT 20;
理想情况下 type 从 ALL 变成 ref 或 range,rows 明显减少。
然后直接看查询耗时,在你的客户端或命令行执行:
SELECT * FROM orders WHERE user_id = 100 ORDER BY create_time DESC LIMIT 20;
对比优化前的耗时。
如果要看整体效果,可以重新统计慢查询日志:清空旧日志,跑一段业务,再统计数量。
TRUNCATE TABLE mysql.slow_log; -- 前提是已开启日志到表
或者直接删除慢日志文件后重建:
rm /var/log/mysql/slow.log
mysqladmin flush-logs
运行半个小时后,用 grep 统计新慢查询条数:
grep "Query_time" /var/log/mysql/slow.log | wc -l
数量明显下降,说明优化生效。
如果还是很多,继续跑分析脚本,一轮一轮迭代。
避坑提醒:别让优化动作反过来拖垮业务
慢查询优化脚本只是帮你发现问题,真正动手改库时要谨慎。
以下几点最容易踩坑:
- 不要在生产环境直接
ALTER TABLE大表。加索引会锁表,建议选择业务低峰期,或用pt-online-schema-change这类工具在线变更。 - 注意磁盘空间。慢查询日志如果长时间不清理,可能占用几十 GB。脚本跑完后及时压缩或定期清理。