做后端开发这么多年,最让我崩溃的不是产品经理改需求,是数据库跑得好好的突然变慢。你以为是代码问题?以为是服务器撑不住了?结果查了半天,发现是索引在骗你。
索引也会骗人?
没错,索引不是万能的。很多人写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垃圾。冷静下来想想:是不是索引没生效?是不是统计数据骗了你?是不是你的查询姿势本身就有问题?
数据库不会撒谎,但索引会骗不会看它的人。