mysql的表扫描指的是数据库在执行查询时,逐行读取表中的全部数据来匹配查询条件,当表数据量较大时,全表扫描会占用大量磁盘IO和CPU资源,导致查询执行效率极低。而索引作为mysql中特殊的存储结构,能够通过有序的数据组织方式,快速定位符合条件的数据行,避免不必要的全表扫描。

mysql索引减少表扫描的核心原理
mysql中最常用的索引类型是B+树索引,它的结构特点是所有数据都存储在叶子节点,并且叶子节点之间通过双向链表连接,非叶子节点仅存储索引键和子节点指针。当执行带查询条件的sql时,mysql会先匹配索引的B+树结构:
- 如果查询条件命中索引,会从索引树的根节点开始向下查找,快速定位到符合条件的索引叶子节点,直接获取对应的数据行地址或者覆盖索引的所需字段,不需要扫描全表
- 如果没有索引,mysql只能从表的第一行开始逐行读取数据,判断是否符合查询条件,直到扫描完所有行,这就是全表扫描
索引减少表扫描的实际场景示例
无索引时的全表扫描
假设有一张用户表user,包含id、name、age、email四个字段,表中有100万条数据,现在需要查询age等于25的所有用户:
-- 无索引时执行查询 EXPLAIN SELECT * FROM user WHERE age = 25;
执行上述EXPLAIN语句后,会看到type字段为ALL,这表示进行了全表扫描,rows字段会显示接近100万,说明mysql需要扫描几乎所有行才能找到符合条件的数据。
创建索引后的扫描优化
给age字段创建普通索引后,再执行相同的查询:
-- 创建age字段的普通索引 CREATE INDEX idx_age ON user(age); -- 再次执行查询查看执行计划 EXPLAIN SELECT * FROM user WHERE age = 25;
此时执行计划的type字段会变为ref,rows字段会显示符合条件的大致行数,远小于100万,说明mysql通过idx_age索引快速定位到了age等于25的数据行,只扫描了极少部分的行,大大减少了表扫描的范围。
通过索引减少表扫描的实用技巧
合理选择索引字段
优先给经常出现在WHERE、JOIN、ORDER BY、GROUP BY子句中的字段创建索引,这些场景下的查询最容易触发全表扫描,索引的收益最高。同时要避免给区分度极低的字段创建索引,比如性别字段只有男、女两个值,创建索引后依然可能需要扫描接近半数的行,优化效果很差。
使用覆盖索引避免回表扫描
如果查询的字段全部包含在索引中,mysql不需要回到原表读取数据,直接通过索引就能返回结果,这种索引叫做覆盖索引,能进一步减少扫描开销。比如上面的查询如果只需要age字段,可以创建包含age的索引,查询时直接返回索引中的值:
-- 创建覆盖索引,包含age和name字段 CREATE INDEX idx_age_name ON user(age, name); -- 查询age和name,不需要回表 EXPLAIN SELECT age, name FROM user WHERE age = 25;
避免索引失效导致全表扫描
很多写法会导致索引失效,让mysql重新走全表扫描,常见的失效场景包括:对索引字段使用函数、索引字段参与运算、使用不等于判断、like以通配符开头、类型隐式转换等。比如下面的写法就会导致idx_age索引失效:
-- 对索引字段使用函数,索引失效,触发全表扫描 SELECT * FROM user WHERE YEAR(age) = 25; -- like以通配符开头,索引失效 SELECT * FROM user WHERE name LIKE '%张三';
索引使用的注意事项
索引虽然能减少表扫描提升效率,但也不是越多越好。每个索引都会占用额外的磁盘空间,而且在执行INSERT、UPDATE、DELETE语句时,mysql需要同时维护所有相关的索引,会增加写操作的开销。因此需要根据实际的查询场景平衡索引的数量,只保留必要的索引,定期清理冗余的索引,避免索引带来的额外负担超过性能收益。