慢SQL通常指执行耗时超过预设阈值的SQL语句,这些语句会在数据库端长时间占用连接、锁和CPU资源,导致其他正常请求排队等待,最终影响整个业务系统的响应速度和吞吐量。不同的业务场景对响应时间的要求差异较大,慢SQL的阈值可以灵活调整,一般建议设置在100毫秒到1秒之间,从而在资源消耗和业务体验之间取得平衡。

慢SQL识别阶段
慢SQL治理的第一步是准确识别慢查询。数据库管理系统提供了多种记录执行耗时的手段,其中最常见的是慢查询日志。开启慢查询日志后,系统会自动将执行时间超过阈值的SQL写入日志文件,开发或运维人员可以定期查看。除此之外,借助数据库监控工具可以实时抓取耗时较长的SQL,并能采集执行频率、影响行数、锁等待等附加信息。在业务代码中埋点统计SQL执行耗时则适用于定位特定功能模块的慢查询问题,能够将SQL与具体业务场景关联起来。
以MySQL为例,开启慢查询日志需要设置几个关键参数。slow_query_log用于控制日志开关,long_query_time用于定义慢SQL阈值,slow_query_log_file用于指定日志存储路径。阈值设置过大会遗漏部分性能问题,设置过小又会产生大量日志,通常需要结合数据库负载和业务敏感度动态调整。配置完成后,可以查询当前配置项确认修改是否生效。
-- 查看当前慢查询日志配置 SHOW VARIABLES LIKE 'slow_query%'; -- 开启慢查询日志,1表示开启,0表示关闭 SET GLOBAL slow_query_log = 1; -- 设置慢查询阈值,单位为秒,这里设置为0.1秒即100毫秒 SET GLOBAL long_query_time = 0.1; -- 设置慢查询日志存储路径 SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
慢SQL分析阶段
识别出慢SQL后,需要分析其执行过程,找到性能瓶颈。MySQL等关系型数据库提供了执行计划工具,通过EXPLAIN命令可以查看优化器对SQL的访问路径和资源估算。执行计划不会真正执行查询,而是展示数据库如何获取数据,这对于判断是否缺少索引、是否进行了全表扫描至关重要。
分析执行计划时,需要重点关注type、key、rows和Extra字段。type表示访问类型,从好到坏大致为system、const、eq_ref、ref、range、index、ALL,出现ALL说明优化器选择了全表扫描,通常意味着需要增加索引或者调整查询条件。key字段表示实际使用的索引名称,如果为NULL说明没有命中索引。rows字段表示预估需要扫描的数据行数,行数越大说明潜在I/O开销越高。Extra字段包含额外的执行信息,例如Using filesort表示需要额外的排序操作,Using temporary表示使用了临时表,这些信号往往指向查询中的排序、分组或去重操作。
有时执行计划中type并非ALL,但Extra中出现了Using filesort或Using temporary,也需要引起重视,因为排序和临时表会消耗大量内存和磁盘资源。对于这类SQL,可以考虑调整排序字段的索引、优化分组条件或重写查询语句,减少额外的排序和临时表创建。
-- 分析查询语句的执行计划,假设查询用户表中年龄大于18的用户 EXPLAIN SELECT * FROM user WHERE age > 18;
慢SQL优化阶段
完成执行计划分析后,可以针对具体原因进行优化。慢SQL优化通常从SQL语句写法、索引设计、表结构等几个维度展开。SQL语句优化目标是在保证业务逻辑正确的前提下减少扫描的数据量和计算开销,索引优化目标则是为查询条件提供高效的访问路径。
SQL语句优化
在编写SQL语句时,应尽量避免全字段查询和高成本操作。SELECT *会读取所有列,当表包含大字段或多列时会造成不必要的网络传输和内存消耗,建议只查询业务真正需要的字段。子查询经常会产生临时表,在数据量较大时性能明显下降,可以尝试改写为关联查询。对字段进行函数操作会阻止索引的正常使用,例如对日期字段使用DATE函数提取日期,优化器难以利用该字段上的索引。大偏移量分页语句如LIMIT 100000, 10会扫描大量无用数据,可以考虑基于主键或唯一索引的游标分页,记录上一页最后一条记录的位置,直接定位下一页数据。
- 避免使用
SELECT *,只查询需要的字段,减少数据传输和解析开销 - 减少子查询的使用,尽量用关联查询替代,避免子查询带来的临时表开销
- 避免对字段进行函数操作,比如
WHERE DATE(create_time) = CURDATE(),会导致索引失效 - 合理使用分页,大偏移量分页可以改用基于主键的游标分页,避免
LIMIT 100000, 10这种全表扫描的分页方式
索引优化
索引优化是处理慢SQL最常用的手段。为查询条件字段建立合适的索引可以大幅减少扫描行数。选择索引列时,应优先选择区分度高的字段,即该字段不同取值占总行数比例较高的字段。对于多个查询条件组合的场景,可以建立联合索引,但联合索引需要遵循最左前缀原则,将最常用且区分度高的字段放在最左侧。
索引并非越多越好,过多的索引会降低写入性能,因为每次插入、更新、删除都需要同步维护索引结构。定期清理冗余索引和无效索引能够降低维护成本。对于长文本字段,直接建立完整索引会占用大量空间,使用前缀索引只索引字段的前几个字符,可以在一定条件下满足查询需求,同时显著减小索引体积。
-- 为user表的age字段创建普通索引 CREATE INDEX idx_user_age ON user(age); -- 为user表的name和age字段创建联合索引 CREATE INDEX idx_user_name_age ON user(name, age); -- 为user表的email字段创建前缀索引,前缀长度为10 CREATE INDEX idx_user_email ON user(email(10));
创建索引后,还需要重新查看执行计划,确认key字段已经使用新索引,type字段从ALL提升为ref或range等更高效率的访问类型。如果执行计划没有变化,可能需要检查索引列是否被函数操作、隐式类型转换或前导通配符等影响。
慢SQL治理步骤
单条慢SQL优化完成后,如果不建立长期治理机制,新的慢SQL会随着业务变更再次出现。慢SQL治理需要从流程和工具两个层面持续推进,形成闭环。
- 建立慢SQL定期巡检机制,定期对慢查询日志进行统一分析,批量处理新出现的慢SQL
- 将慢SQL优化纳入需求上线前的评审流程,对新上线的SQL语句提前进行性能评估
- 建立慢SQL治理台账,记录每条慢SQL的问题原因、优化方案、优化前后的耗时对比,方便后续复盘
- 对核心业务接口的SQL执行耗时设置监控告警,一旦出现慢SQL立即通知相关人员处理
- 定期对数据库的统计信息进行更新,保证执行计划的准确性,避免因为统计信息过期导致的索引失效问题
慢SQL治理台账能够沉淀优化经验,每条记录包含问题原因、优化方案、优化前后耗时对比,便于后续遇到类似问题时快速定位。核心业务接口的SQL执行耗时监控可以做到实时告警,一旦执行时间超过阈值,相关人员可以第一时间介入处理,避免将性能问题扩大为线上事故。
数据库统计信息的准确性对执行计划的选择影响很大,如果统计信息过期,优化器可能选择错误的索引甚至放弃索引。定期更新统计信息,可以保证执行计划稳定,减少因统计信息不准导致的慢SQL反弹。
优化效果验证
优化完成后必须验证效果。可以再次使用EXPLAIN查看执行计划,确认访问类型和索引使用情况是否符合预期。实际执行SQL并记录耗时,与优化前进行对比,确认执行时间已经降低到阈值以下。如果查询结果集较大,还应关注结果数据是否与优化前完全一致,避免因为改写SQL而引入逻辑错误。
索引调整和查询改写可能带来副作用,例如新增索引会占用存储空间并降低写入吞吐,关联查询改写可能改变结果集或增加单次查询复杂度。因此,验证阶段可以使用压测工具模拟真实业务流量,观察数据库整体性能表现,判断优化是否在提升查询速度的同时,没有对其他写入和查询造成明显影响。
-- 优化后再次查看执行计划,确认type字段不再是ALL,key字段有实际使用的索引 EXPLAIN SELECT id, name FROM user WHERE age > 18; -- 直接执行SQL查看实际耗时 SELECT id, name FROM user WHERE age > 18;
慢SQL优化是一个从识别、分析、优化、治理到验证的闭环过程。通过合理配置慢查询日志并借助执行计划定位问题,再结合SQL语句改写和索引设计进行针对性优化,可以减少数据库资源消耗,提升系统整体稳定性。长期的治理机制能够帮助团队持续发现和解决新出现的慢查询,将性能风险控制在可接受范围内。建议团队将慢SQL治理纳入日常运维和上线评审流程,形成规范化的性能保障体系。