- 约1204字
- 技术
- 2026年3月27日
上周帮一个朋友排查问题,他的订单查询页面加载要12秒。查了代码,写得没问题啊,索引也建了,怎么还是慢?我让他把慢查询的EXPLAIN结果发给我一看——好家伙,索引根本没生效。
1. 最常见的坑:索引列上做了函数运算
很多人会这样写查询:
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
SELECT * FROM users WHERE SUBSTRING(name, 1, 3) = '张';
这样索引列参与了运算,数据库只能用全表扫描。正确的做法是把运算移到右边:
SELECT * FROM orders WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
如果你真的需要对索引列做函数运算,考虑用函数索引(MySQL 8.0+)或表达式索引(PostgreSQL)。
2. 字符串类型不匹配
这是个容易被忽视的问题。
假设你有个user_id字段是varchar类型存的是数字,然后你这样查:
SELECT * FROM orders WHERE user_id = 12345;
注意12345是数字,数据库在做比较时会做隐式类型转换,把varchar转成数字。这个转换会导致索引失效。
正确写法:
SELECT * FROM orders WHERE user_id = '12345';
加引号,确保类型一致。
3. 联合索引顺序写反了
很多人知道建联合索引,但不知道顺序很重要。
假设有张用户表,查询条件是WHERE status = 'active' AND age > 18,你建了索引(age, status),那这个索引根本用不上——因为数据库是按从左到右的顺序匹配,你第一个条件是age,但查询第一个条件是status。
原则:把区分度高的列放前面, WHERE条件最频繁的列放前面。
更好的做法是建两个索引:(status, age) 或 (status) + (age),看查询频率。
4. LIKE通配符开头的模糊匹配
SELECT * FROM products WHERE name LIKE '%手机%';
这种以%开头的模糊匹配,索引是无效的。只能走全表扫描。
如果你确实需要全文搜索,有两个替代方案:
- 用Elasticsearch等搜索引擎
- 如果是MySQL 5.7+,可以建generated column + 函数索引
ALTER TABLE products ADD COLUMN name_reverse VARCHAR(255) GENERATED ALWAYS AS (REVERSE(name));
CREATE INDEX idx_name_reverse ON products(name_reverse(10));
查询时:
SELECT * FROM products WHERE name_reverse LIKE REVERSE('%手机%');
5. 范围查询放在最后
联合索引中,如果某个列使用了范围查询(>、<、BETWEEN),那它右边的列就很难用到索引。
假设索引是 (status, created_at, user_id),查:
WHERE status = 'active' AND created_at > '2026-01-01' AND user_id = '123'
只有前两列能用到索引,user_id这列是走不了索引的。
如果可能,把范围查询放到最后,或者拆分查询。
怎么快速定位索引问题
上面5个是最常见的,但不是全部。我的建议是:
- 慢查询日志:打开MySQL的slow_query_log,把超过1秒的SQL都记录下来
- EXPLAIN分析:每个慢查询都跑一遍EXPLAIN,看type列是不是ALL(全表扫描)
- 索引统计信息:定期执行 ANALYZE TABLE,让数据库更新索引统计信息
-- MySQL查看查询执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = '12345';
-- 更新统计信息
ANALYZE TABLE orders;
索引不是万能的,建了不一定用,用了不一定对。关键是要理解索引的原理,知道哪些操作会让它失效。
下次遇到查询慢,别急着改代码重构,先跑一遍EXPLAIN,看看索引是不是真的生效了。90%的性能问题,改改SQL写法就能解决。