在实际开发过程中,很多工程师都会遇到类似的问题:明明已经给数据库字段建立了索引,但查询速度并没有明显提升,甚至在数据量增长后,原本流畅的 SQL 开始出现明显延迟。
很多人第一反应是"索引没有创建成功"或者"数据库性能不够",但真正的问题往往出现在 SQL 的执行方式上。索引并不是创建之后就一定会被使用,它只是数据库优化器提供的一种查询路径选择。当优化器判断全表扫描成本更低时,即使存在索引,也可能直接放弃使用。
理解索引失效的原因,需要从数据库如何执行 SQL 开始。
索引到底解决了什么问题
数据库中的数据通常存储在磁盘或内存页中。如果没有索引,当执行类似下面的查询:
sql
SELECT * FROM user WHERE username = 'Tom';
数据库需要逐行检查 user 表中的数据,判断 username 是否等于 Tom。
当表中只有几千条数据时,这种方式影响并不明显。但如果数据量达到百万级甚至千万级,全表扫描意味着数据库需要读取大量无关数据,查询时间会随着数据规模增长。
索引的作用,就是建立一个额外的数据结构,让数据库可以快速定位目标数据。
以 MySQL 常见的 B+Tree 索引为例,它并不会保存完整的数据记录,而是按照索引字段进行排序,并保存指向真实数据位置的引用。
查询时,数据库不需要从第一行开始查找,而是通过树结构快速定位目标范围,再读取对应的数据。
这也是为什么建立合理索引后,查询效率可以从秒级降低到毫秒级。
但是,索引并不是免费的。
索引需要额外占用存储空间,同时在新增、修改、删除数据时,数据库还需要维护索引结构。因此,索引设计的核心不是"越多越好",而是让数据库在查询成本和维护成本之间达到平衡。
为什么有索引却没有使用
最常见的问题之一,就是 SQL 写法导致索引无法发挥作用。
例如:
sql
SELECT * FROM user WHERE name LIKE '%Tom';
虽然 name 字段存在索引,但这个查询通常无法利用普通 B+Tree 索引。
原因在于索引是按照从左到右的顺序排列的。当查询条件以固定字符串开头时,例如:
sql
SELECT * FROM user WHERE name LIKE 'Tom%';
数据库可以快速定位以 Tom 开头的数据范围。
但如果使用:
sql
LIKE '%Tom'
数据库无法提前确定 Tom 所在的位置,只能扫描大量数据,因此索引效果会明显下降。
类似的问题还包括对索引字段进行函数处理:
sql
SELECT * FROM user
WHERE DATE(create_time) = '2026-09-29';
这里虽然 create_time 建立了索引,但 DATE() 函数改变了字段本身的值,数据库无法直接通过索引定位。
更合理的写法是:
sql
SELECT * FROM user
WHERE create_time >= '2026-09-29 00:00:00'
AND create_time < '2026-09-30 00:00:00';
这种范围查询可以继续利用索引结构。
联合索引为什么容易踩坑
实际业务中,经常需要根据多个字段查询。
例如用户订单查询:
ini
SELECT *
FROM orders
WHERE user_id = 1001
AND status = 'paid';
很多开发者会建立联合索引:
scss
CREATE INDEX idx_user_status
ON orders(user_id, status);
这个设计通常是合理的。
但是联合索引遵循"最左匹配原则"。
如果查询变成:
ini
SELECT *
FROM orders
WHERE status = 'paid';
数据库无法直接利用 idx_user_status,因为索引第一列是 user_id,而查询没有提供 user_id 条件。
可以把联合索引理解成一本按照"用户编号→订单状态"排序的通讯录。
如果你知道用户编号,可以快速找到对应区域,再继续查状态。
但如果你只知道状态,却不知道用户编号,就无法直接定位。
因此,在设计联合索引时,需要结合真实查询场景,而不是简单按照字段数量创建。
如何通过执行计划判断索引是否生效
很多开发者优化 SQL 时,只关注执行结果,却忽略数据库实际采用了什么方式。
在 MySQL 中,可以通过:
sql
EXPLAIN SELECT *
FROM user
WHERE username='Tom';
查看执行计划。
其中几个关键字段非常重要。
type 表示访问类型。
常见类型包括:
- ALL:全表扫描
- index:扫描整个索引
- range:范围查询
- ref:通过索引快速匹配
- const:常量级查询
通常情况下,ALL 是需要重点关注的情况,因为它意味着数据库正在扫描大量数据。
key 字段表示实际使用的索引。
如果建立了索引,但 key 为空,说明优化器没有选择该索引。
rows 字段表示预计扫描的数据数量。
例如:
makefile
rows: 5000000
说明数据库可能需要检查五百万行数据。
而优化后的查询:
makefile
rows: 10
则意味着索引已经帮助数据库缩小了搜索范围。
数据量小的时候,索引可能反而没有优势
一个容易被忽视的问题是,小表不一定需要索引。
例如:
bash
user_config
-------------
id
key
value
如果表中只有几十条配置数据,即使没有索引,数据库扫描几十行数据的成本也非常低。
此时建立索引不仅不会明显提升性能,反而增加了维护成本。
数据库优化不是简单追求更多索引,而是根据业务规模和访问模式进行设计。
真正需要优化的,通常是高频访问、大数据量、查询复杂的核心表。
索引优化不仅是数据库问题
很多 SQL 性能问题,并不是数据库单方面造成的。
例如接口一次查询大量无用字段:
sql
SELECT *
FROM user;
可能比:
sql
SELECT id,name
FROM user;
消耗更多资源。
因为数据库需要读取更多数据,网络也需要传输更多内容。
再比如分页查询:
vbnet
SELECT *
FROM article
ORDER BY id
LIMIT 100000,20;
随着偏移量增加,数据库需要先扫描大量数据,再丢弃前面的结果。
对于大规模数据,可以采用基于游标的分页:
vbnet
SELECT *
FROM article
WHERE id > 100000
ORDER BY id
LIMIT 20;
这种方式可以让数据库继续利用索引快速定位。
因此,数据库性能优化往往涉及数据结构、SQL 写法、业务逻辑以及系统架构多个层面。
如何建立更加合理的索引体系
实际项目中,索引设计应该从业务查询出发。
开发前期,可以根据主要业务场景整理高频 SQL,分析哪些字段经常作为查询条件、排序条件和关联条件。
对于频繁查询但数据变化较少的字段,可以考虑建立索引。
对于区分度低的字段,需要谨慎处理。
例如:
markdown
gender
---------
男
女
如果一个用户表有几千万数据,通过 gender 查询通常无法有效减少扫描范围,因为每个类别的数据量都很大。
而用户手机号、订单编号这类唯一性较高的字段,更适合作为索引。
同时,索引建立后也需要持续观察。
随着业务发展,数据分布会发生变化,过去有效的索引可能不再适合当前环境。
数据库优化不是一次性的工作,而是伴随业务增长持续调整的过程。
总结
索引是数据库性能优化中最重要的工具之一,但它并不是简单的"加一个索引就变快"。
真正影响索引效果的,是 SQL 写法、数据分布、查询模式以及数据库优化器的判断。
理解执行计划,比盲目增加索引更加重要。
当数据库出现慢查询时,与其立即增加更多索引,不如先分析 SQL 的实际执行路径,找到真正消耗资源的位置。
好的数据库设计,不是让所有查询都使用索引,而是在合适的场景下,让数据库选择最合理的访问方式。