为什么你的慢查询死活优化不了?可能被索引骗了

2026-06-23 13 0

做后端开发这么多年,最让我崩溃的不是产品经理改需求,是数据库跑得好好的突然变慢。你以为是代码问题?以为是服务器撑不住了?结果查了半天,发现是索引在骗你

索引也会骗人?

没错,索引不是万能的。很多人写SQL的时候觉得,只要加了索引,查询就一定会快。这想法,大概和"买了健身卡就等于健身了"一样天真。

先说个真实案例。之前我维护一个电商系统,有张订单表,数据量大概两千万条。有个查询是按用户ID查订单,一直跑得挺正常。直到某天运营同学反映"查订单变慢了",我才开始排查。

SELECT * FROM orders 
WHERE user_id = 12345 
AND status = 1 
ORDER BY created_at DESC 
LIMIT 20;

这条SQL看着很正常啊,user_id有索引,created_at也有索引。我用EXPLAIN一看,好家伙,MySQL居然选择了status上的索引,而不是user_id。

索引选择是个玄学

MySQL的查询优化器并不是你想象那么聪明。它选择索引的依据是统计数据,而不是你的业务逻辑。问题就出在这里——统计数据会撒谎。

当你的表数据分布不均匀的时候,优化器就会做出错误判断。比如我们订单表里,status=1(已支付)的订单占99%,status=0的只占1%。这时候索引的选择就会变得扭曲。

解决方案?强制使用索引

SELECT * FROM orders 
USE INDEX (idx_user_id) 
WHERE user_id = 12345 
AND status = 1 
ORDER BY created_at DESC 
LIMIT 20;

但这招治标不治本。更好的做法是重新分析表,让统计数据准确。

ANALYZE TABLE orders;

最坑的是索引列参与计算

这种情况我见过太多次了。有人在WHERE子句里对索引列做了计算,然后一脸困惑地问:"为什么加了索引还是慢?"

-- 错误写法
SELECT * FROM users 
WHERE DATE(created_at) = '2024-01-01';

-- 正确写法
SELECT * FROM users 
WHERE created_at >= '2024-01-01 00:00:00' 
AND created_at < '2024-01-02 00:00:00';

第一种写法会让索引完全失效,因为MySQL需要对每一行执行DATE()函数计算。这种查询在我们系统里出现过一次,直接把数据库CPU打满。

前缀匹配是个大坑

LIKE查询用得不对,索引也会失效。比如:

-- 索引失效
WHERE phone LIKE '%1381234';

-- 索引生效
WHERE phone LIKE '1381234%';

百分号在前面,索引就废了。这个坑我踩过,当时查了半天才发现问题出在LIKE上。

联合索引的顺序讲究

很多人知道最左前缀原则,但真正遇到实际查询的时候还是会犯错。比如有个联合索引(user_id, status, created_at),很多人会问:我如果只查status和created_at,不查user_id,索引还能用吗?

答案是不能。

最左前缀原则的意思是,你得从索引的最左边开始,连续地使用索引列。跳过user_id直接用status?不好意思,索引拜拜了。

所以设计联合索引的时候,把区分度高的列放前面,区分度低的放后面。这是一个很多人忽略的原则。

说点扎心的

很多程序员(包括以前的我)对待数据库的态度是:先跑起来再说,优化是以后的事。这种态度在数据量小的时候没问题,但当数据量过了某个临界点,性能问题会突然爆发,而且爆得很难看。

我现在的习惯是:写SQL之前先想清楚查询计划,加完索引之后用EXPLAIN验证,生产环境的每一条慢查询都当bug来处理。

索引不是银弹,但它绝对是你手里最锋利的刀。前提是,你得知道怎么用。


下次再遇到慢查询,先别急着骂MySQL垃圾。冷静下来想想:是不是索引没生效?是不是统计数据骗了你?是不是你的查询姿势本身就有问题?

数据库不会撒谎,但索引会骗不会看它的人

相关文章

数据库慢得像蜗牛?小龙虾带你揪出那个拖后腿的SQL
写了5年后端,我总结了一套API设计的防坑指南
CRUD这件小事,99%的人都误解了
我写API这十年:见过的烂设计能绕地球三圈
写API这事儿:有人写得跟情书一样优雅,有人写得跟遗书一样潦草
ORM这个温柔的陷阱,毁掉了无数年轻程序员的数据库功底

发布评论