前言:索引不是万能药
刚工作那会儿,我觉得给表加上索引就万事大吉了。后来被现实狠狠教育了一顿——加了索引的查询依然跑30秒,加索引的位置不同性能差了100倍。今天来聊聊那些让你EXPLAIN出来看得懂但实际跑出来想骂娘的索引失效场景。
一、最经典的:函数套在索引列上
先看代码:
SELECT * FROM orders WHERE YEAR(created_at) = 2024;
SELECT * FROM users WHERE LEFT(name, 4) = "张";
SELECT * FROM products WHERE price * 1.2 > 100;
这三条语句,索引列被函数包住了。虽然你在created_at、name、price上都建了索引,但MySQL跟你说:抱歉,我用不了。
原因很直白:B+树存的是原始值,你用函数处理过之后,数据库得逐行扫描才能知道哪些符合条件。索引:我也很无奈啊。
解决方案:把函数改成范围查询,或者提前算好值存成新列。比如第一条改成:
SELECT * FROM orders WHERE created_at >= "2024-01-01" AND created_at < "2025-01-01";
或者老老实实用BETWEEN。
二、隐式类型转换:数字和字符串的恩怨
这条坑了无数人:
SELECT * FROM products WHERE product_id = 12345; -- product_id是varchar
SELECT * FROM users WHERE phone = 13800138000;
你以为传的是数字,数据库存的是字符串。MySQL在比较前会把字符串转成数字——这下好,索引列上的每一条记录都要转换一遍再比较,全表扫描走起。
类型要一致,血泪教训。传什么就存什么,或者CAST清楚。
三、范围查询后边的列:用了个寂寞
假设有个联合索引(status, created_at, user_id),你这么写:
SELECT * FROM orders WHERE status = 1 AND created_at > "2024-01-01" AND user_id = 100;
MySQL用了前两个条件,第三个?不好意思,用不上了。范围查询断掉了后边的列的索引使用。
最佳实践:把范围条件放到后边。如果逻辑上必须是范围,就考虑拆成两条SQL或者用延迟关联。
四、最左前缀原则:你以为你以为的就是你以为的?
索引是(a, b, c),但你这么查:
WHERE b = 10 AND c = 20
索引直接废掉。MySQL只从最左边开始匹配。你得写成:
WHERE a = 5 AND b = 10 AND c = 20
或者用index(a),单独用a也能走索引。记住:联合索引,最左匹配。
五、数据量太小:索引反而拖后腿
表才1000行,全表扫描可能比索引还快——因为索引本身要回表,要跳转磁盘。所以MySQL的优化器会说:你这表太小,我看不上索引。
要不要索引,看数据量和查询频率。几百行的小表,直接全表扫描反而爽。
六、LIKE开头是%,神仙也救不了
WHERE name LIKE "%小龙虾" -- 开头是%
WHERE name LIKE "小龙虾%" -- 开头不是%,能用索引
前者是反向扫描,后者是正向扫描。懂了吧?前缀模糊匹配可以,后缀模糊匹配索引救不了,要么上全文索引,要么换Elasticsearch。
七、OR条件:最容易被忽视的坑
WHERE (status = 1 OR id = 100)
OR条件如果没有全部命中索引列,会变成全表扫描。解决方案:用UNION替代OR,或者把OR改成IN。
八、Cardinality(选择性)太低:索引不值钱
假设有个性别字段,只有男和女两个值,索引Cardinality极低。MySQL优化器会判断:全表扫描可能更快,何必去走索引?于是索引被无视。
高选择性的字段建索引才有意义,比如用户ID、订单号、商品ID等。
总结:索引优化的核心思路
- 查询条件里别套函数,别做运算
- 类型要匹配,字符串就字符串,数字就数字
- 范围查询放后边,OR改成UNION
- 联合索引注意最左前缀
- 写完SQL跑一下EXPLAIN,看看有没有全表扫描
- 数据量小的时候别硬上索引
索引调优是个技术活,也是个耐心活。别以为加了索引就完事了——MySQL优化器不傻,它会用脚投票。真正理解索引的工作原理,才能写出跑得飞起的SQL。
下次再遇到30秒的查询,先别骂数据库,先看看EXPLAIN。