最常见的原因是没加索引或索引失效导致全表扫描:WHERE、ORDER BY、JOIN 字段无有效索引时MySQL需逐行扫描;EXPLAIN显示type为ALL、key为NULL、rows远超匹配数即为典型表现;复合索引须遵守最左前缀原则;OR条件中任一字段无索引、对索引字段用函数、隐式类型转换均会导致索引失效;SELECT *或深分页(如LIMIT 100000,20)会大幅降低性能,应只查必要字段并改用游标分页。

没加索引或索引失效导致全表扫描
这是最常见的原因:当 WHERE、ORDER BY、JOIN 字段上没有有效索引,MySQL 就只能一行行读完整张表。哪怕只有几万行,I/O 和 CPU 开销也会陡增。
检查方法很简单:EXPLAIN SELECT ... 看 type 是否为 ALL(全表扫描),key 是否为 NULL,rows 是否远超实际匹配数。
- 复合索引要注意最左前缀原则,
(a,b,c)无法加速WHERE b = ? -
OR条件中只要有一边没索引,整条语句可能退化为全表扫描 - 对索引字段做函数操作(如
WHERE YEAR(create_time) = 2024)会直接让索引失效 -
隐式类型转换(比如字符串字段查数字:
WHERE mobile = 13800138000)也可能绕过索引
查询返回了太多无用数据
用 SELECT * 或没加 LIMIT 的分页查询,在数据量大时非常危险。网络传输、内存排序、客户端处理都会拖慢整体响应。
典型例子:SELECT * FROM orders ORDER BY created_at DESC LIMIT 100000, 20 —— MySQL 仍要先定位前 100000 行,再取 20 行,效率极低。
- 只查真正需要的字段,避免
* - 深分页优先改用游标方式(比如记录上一页最大
id,下一页查WHERE id ) - 大结果集导出类需求,考虑用
mysqldump或服务端流式读取,别在应用层一次拉完
锁等待和事务长跑阻塞查询
慢查询不一定是 SQL 本身慢,也可能是被别的连接卡住了。比如一个未提交的事务长时间持有行锁/表锁,后续所有涉及这些行的 SELECT ... FOR UPDATE 或写操作都会排队等待。
查当前阻塞情况:SHOW ENGINE INNODB STATUS\G 看 TRANSACTIONS 部分;或者查 information_schema.INNODB_TRX 和 INNODB_LOCK_WAITS。
- 避免在事务里做 HTTP 调用、文件读写等外部耗时操作
- 写操作尽量短平快,别把
UPDATE套在大循环里 - 读多写少场景可考虑开启
READ COMMITTED隔离级别,减少锁范围 - 注意
AUTO_COMMIT=OFF后忘记COMMIT的“幽灵事务”
服务器资源或配置不合理
即使 SQL 写得再好,硬件跟不上或参数设错也会拖慢查询。比如 sort_buffer_size 太小导致 ORDER BY 落盘排序,innodb_buffer_pool_size 远小于数据量造成频繁磁盘读。
关键指标要看:SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'(磁盘读次数) vs 'Innodb_buffer_pool_read_requests'(总读请求),比值超过 1% 就说明缓存严重不足。
- 缓冲池大小建议设为物理内存的 50%–75%,但别超过可用内存,否则触发系统 OOM
- 临时表过大(
Created_tmp_disk_tables高)说明tmp_table_size和max_heap_table_size设太小 - 高并发下
innodb_thread_concurrency设为 0(默认不限制)通常更稳,设死值反而可能限制吞吐
慢查询背后往往是多个因素叠加,比如一条没索引的语句在缓冲池不足时,既触发大量磁盘 I/O,又因排序落盘进一步放大延迟。定位时别只盯着 SQL,得从执行计划、锁状态、服务器指标三层一起看。










