为什么会出现慢SQL?我们来细说这个问题。慢SQL,不是看“执行时间长”这个表象,而是数据库在处理某条SQL时,因为访问路径低效、计算负担过重、资源争用或优化器误判,导致其执行效率远低于预期。要真正理解“为什么”,必须深入SQL的执行机制与底层原理。下面,以MySQL为例介绍。仅供参考。
一、MySQL执行SQL的底层流程
一条SQL从客户端发出到返回结果,需要经历以下8个步骤:
- 连接建立:客户端通过TCP连接MySQL(默认3306端口),由连接池管理;
- 查询缓存(MySQL 8.0+已移除):如果命中缓存,则直接返回(但是因为表级写操作会直接清空整张表的所有查询缓存、缓存键要求精准字符串匹配且与OLTP系统高频读写的业务特性高度冲突,导致查询缓存极易失效,对OLTP系统作用有限);
- 语法解析:词法分析 → 语法分析 → 生成语法树;
- 查询优化:关键环节!优化器基于统计信息和成本模型,决定执行路径(如:走哪个索引、JOIN顺序等);
- 执行器执行:调用存储引擎(如:InnoDB)接口,按计划逐行或批量读取数据;
结论:慢SQL的表面直接原因,主要表现为第7步(执行器执行效率低),而根源通常在第5步(优化器生成的执行计划不佳)。其余步骤很少成为性能瓶颈。
二、慢SQL出现的根本原因
慢SQL的根源,可以归结为以下四类问题,对应公式为:慢SQL = 等(等待) + 扫(扫描) + 算(计算) + 错(误判)。
1、等(资源等待)
SQL本身没问题,但是因为外部依赖阻塞而变慢。
- 锁等待:行锁/表锁被其他事务持有(如:长事务更新未提交);DDL操作(如:
ALTER TABLE)触发MDL锁,阻塞DML/SELECT。 - 连接等待:应用连接池配置过大或连接未及时释放,导致数据库
max_connections被占满,新查询排队。 - I/O等待:
Buffer Pool太小,热点数据无法缓存,大量请求穿透到磁盘;脏页刷盘(因为Redo Log满或内存压力)占用I/O带宽,拖慢查询。
表现:CPU不高,但是响应时间长;SHOW PROCESSLIST中状态多为Waiting for table metadata lock、Lock wait等;或执行器长时间处于Sending data状态(扫描大量数据)。
2、扫(数据访问低效)
最常见、最根本的原因:访问了远远超过必要的数据量。
- 全表扫描:查询条件字段无索引;索引存在但是失效(如:
WHERE name = 123(name是字符串)、LIKE '%abc'、WHERE YEAR(create_time) = 2023)。 - 低效索引使用:索引区分度低(如:对“性别”建索引);二级索引未覆盖查询字段,导致大量回表(先查索引再查聚簇索引);深分页(
LIMIT 100000, 10)需要跳过大量行。 - 随机I/O过多:非顺序范围查询 + 回表 → 大量磁盘随机读,性能远远低于顺序读。
底层机制:MySQL与磁盘交互的最小单位是页(默认16KB)。即使只查一行,也可能加载整页,造成I/O浪费;但是如果该页已在Buffer Pool中,则为内存操作;如果不在,则触发磁盘IO。
表现:磁盘I/O利用率>80%,EXPLAIN中type=ALL或Extra=Using where。
3、算(计算开销过大)
即使数据访问路径正确,复杂计算也会拖垮性能。
- 内存/磁盘排序:
ORDER BY字段无索引 → 触发Using filesort;如果排序数据量超过sort_buffer_size,会写入磁盘临时文件。 - 临时表创建:复杂子查询、
GROUP BY + ORDER BY不一致等 → Using temporary;临时表超出tmp_table_size → 落盘,性能骤降。 - 聚合与函数计算:
COUNT(*)在大表上无索引优化;Server层计算陷阱:在WHERE中使用引擎无法下推的表达式(如:column_a + 1 = 10),即使column_a有索引,也需要全量读取后在Server层计算,完美融合“扫+算”的双重低效。
表现:CPU持续>90%,EXPLAIN中出现Using filesort或Using temporary。
4、错(优化器误判)
优化器“聪明反被聪明误”,生成了非最优执行计划。
- 统计信息过期:大量INSERT/DELETE后未更新统计信息(
ANALYZE TABLE);优化器误判行数,选择全表扫描而非索引。 - JOIN顺序错误:本来应该“小表驱动大表”,却反过来,导致大表被多次扫描;多表关联时无法有效使用索引。
- 索引选择错误:存在多个索引时,优化器选择了区分度更低的那个;强制走索引(
FORCE INDEX)反而更慢,说明优化器原本判断正确。
「快速验证与临时解决技巧」
验证优化器成本估算:查看详细决策依据
用EXPLAIN FORMAT=JSON 我们的SQL语句;执行,输出结果中会包含优化器对各执行路径的成本计算(如:cost_info、rows_estimated),可以精准判断优化器是否因为“误判行数/数据分布”选错路径(例:实际符合条件仅10行,优化器估算10万行,进而放弃索引)。
修复统计信息过期:强制更新表统计数据
如果确认是统计信息过时导致误判,执行ANALYZE TABLE表名;(InnoDB/MyISAM均支持),该命令会重新收集表的数据分布、行数、索引区分度等统计信息,优化器基于新统计信息通常能生成最优执行计划(执行无锁,对生产环境影响极小)。
临时修正执行计划:谨慎使用索引提示
如果优化器选错索引/关联顺序,我们可通过索引提示(Hint)临时指定执行路径(如:FORCE INDEX(索引名)强制走某索引、STRAIGHT_JOIN强制按SQL书写顺序做表关联),但是仅作临时解决方法,需要后续重构SQL/优化索引,避免过度依赖Hint导致优化器失效。
表现:有索引但是没有使用,或使用了“看起来不合理”的索引;我们可通过EXPLAIN FORMAT=JSON查看成本估算。
三、慢SQL的表现
资源瓶颈是慢SQL的表现,而非根本原因,与慢SQL出现的根本原因之间的因果关系如下:
优化慢SQL,要治“病根”(索引、SQL写法、执行计划),而不是只“退烧”(加CPU、扩内存)。
四、慢SQL判定标准
“慢”是相对的,取决于场景容忍度,由业务决定“慢”:
最佳实践:
- 生产环境统一设置
long_query_time = 1; - 开启
log_queries_not_using_indexes,捕获潜在风险SQL; - 结合95分位响应时间监控,避免平均值掩盖长尾问题。
- 云时代新视角:在RDS等云数据库中,“慢SQL”也指高资源消耗SQL(即使执行快),因为其直接影响成本与稳定性。
总结:慢SQL的四大根源
表格总结:
| | | |
|---|
| | | 缩短事务、避免DDL高峰、增大Buffer Pool |
| | type=ALL | |
| | Using filesort/temporary | |
| | | |
文本总结:
慢SQL的出现,是SQL查询的声明式意图与MySQL实际执行路径发生错配的结果。其本质可归结为“等、扫、算、错”四大根本原因:
- 等(阻塞):查询因锁竞争、连接耗尽或I/O排队等外部资源等待而停滞,无法执行。
- 扫(过量访问):由于索引缺失、失效或低效,导致存储引擎被迫加载远远超过所需的数据页,引发大量冗余I/O。
- 算(负重计算):在Server层进行了复杂的排序、聚合或函数处理,消耗过量CPU与内存,甚至因溢出而使用缓慢的磁盘临时表。
- 错(路径误选):优化器基于过时或不准确的统计信息,或因其成本模型无法评估缓存效应,选择了实际效率更低的执行计划。
阅读原文:原文链接
该文章在 2026/2/4 15:48:21 编辑过