MySQL什么时候索引失效反而提升效率?
1.小数据量表
表中的数据量非常小,通常几十条或者几百条记录
对小表全表扫描的成本可能低于使用索引的成本。因为使用索引需要:
先查索引树
在回表读取实际数据
注意:
主键索引确实不用"回表",但它依然有"过路费"
正如你所说,在 InnoDB 引擎中,主键索引就是聚簇索引,它的叶子节点直接存储了完整的行数据。因此,通过主键查询(例如 `SELECT * FROM table WHERE id = 1`)确实**不需要回表**,可以直接拿到数据。
但即使不走回表,走主键索引依然会产生以下开销:
- B+树的遍历开销:数据库需要从根节点出发,经过非叶子节点,一层层定位到叶子节点。
- 逻辑读与随机 I/O:索引查询本质上是一种"随机读取"(Random I/O)。
当表非常小(比如只有几十条数据)时,整个表的数据可能仅仅存放在 1 个或极少数的几个数据页(Page)中。此时,如果优化器选择"全表扫描",它只需要做一次简单的**顺序 I/O**,把这几个数据页直接读到内存里遍历一遍即可。
2.索引选择性差
- 现象:某列的重复值太多,比如
gender字段(值只有 "M" 和 "F")。 - 原因:
- 索引选择性低,意味着索引无法显著减少扫描的行数。
- 全表扫描可能比使用索引更快,因为索引在这种情况下会扫描很多行,然后再回表。
- 优化建议:
- 如果查询范围非常大(比如查大部分记录),可以忽略索引。
- 如果查询小范围,可以使用覆盖索引。
3.查询条件无法利用索引
- 现象:查询中包含的条件无法用索引优化,或者查询条件与索引不匹配。
- 例子:
- 使用函数或表达式:
WHERE LEFT(name, 1) = 'A' - 数据类型不一致:
WHERE id = '123'(id是数字类型)
- 使用函数或表达式:
- 原因:
- MySQL 无法使用索引,直接退化为全表扫描。
- 优化建议:
- 修改查询条件,避免函数操作或类型不匹配。
4.order by+limit特殊场景
- 现象:某些排序加分页的查询中,MySQL 选择全表扫描而非使用索引。
- 原因:
- 如果索引的列顺序无法完全支持排序(ORDER BY)或 LIMIT 的使用,MySQL 可能选择全表扫描。
- 优化建议:
-
确保索引与查询的排序条件和筛选条件匹配。
情况一:正常的优化器权衡利弊(为了偷懒)
当你的 SQL 语句中同时包含 ORDER BY 主键ID 和 LIMIT n(且 n 很小)时,MySQL 优化器可能会认为全表扫描反而更快。
核心逻辑:InnoDB 引擎的主键索引(聚簇索引)本身就是按 ID 排好序的。如果你要求 ORDER BY id ASC,优化器会觉得:"既然数据本来就是按 ID 排好序的,我直接顺着主键索引(也就是全表扫描)从头开始找,找到前 N 条就可以直接返回了,完全省去了额外的排序(Using filesort)开销。"
为什么有时候会翻车:优化器的这种"偷懒"策略有一个前提------它天真地以为加上 LIMIT 后,只需要扫描很少的数据就能找到符合条件的 N 条记录。但如果你的 WHERE 条件过滤性很差(比如查出来的数据分布很散),优化器可能不得不扫描大量数据才能凑齐这 N 条,这时全表扫描的成本就远远高于"走普通索引 + 回表 + 排序"了。
情况二:著名的 MySQL 优化器 Bug
这是一个在 MySQL 5.7 和 8.0 早期版本中长期存在且非常坑的 Bug。
现象:当你写 ORDER BY id ASC LIMIT 1(或者 n 很小)时,即使你有非常完美的普通索引(比如 idx_uid),优化器也会无视索引成本,强制选择走主键索引进行全表扫描。
原因:优化器在计算成本时,错误地评估了 reconsidering_access_paths_for_index_ordering(重新考虑索引排序路径)这一步的代价,盲目认为避免排序就能赢,结果导致了严重的性能倒退。
如何优化与解决?
如果你发现 ORDER BY + LIMIT 导致走了全表扫描且性能很差,可以尝试以下几种方案:- 建立完美的联合索引(最推荐)
确保你的索引能同时满足 WHERE 筛选和 ORDER BY 排序。
原则:把 WHERE 的等值查询字段放前面,ORDER BY 的字段紧随其后。
例子:SELECT * FROM orders WHERE user_id = 10086 ORDER BY create_time DESC LIMIT 20;
最佳索引:CREATE INDEX idx_user_time ON orders(user_id, create_time DESC);
效果:数据库可以直接按索引顺序读取前 20 条,既不用全表扫描,也不用额外排序。 - 避开 Bug 的 Trick(针对情况二)
如果你不幸遇到了上述的优化器 Bug,可以通过"欺骗"优化器来强制它走正确的索引:
方法一:使用 FORCE INDEX(你的索引名) 强制指定索引。
方法二(更优雅):将 ORDER BY id 改写为 ORDER BY (id+0)。因为给字段加了数学运算,优化器会认为无法直接利用主键的有序性来偷懒,从而乖乖地去评估其他索引的成本,最终往往能选对索引。 - 拒绝 SELECT
如果查询的字段过多,无法走"覆盖索引",数据库就必须频繁回表。在 LIMIT 较大时,回表的开销会急剧放大,导致优化器更容易做出错误的判断。尽量只查询需要的字段。
你可以先用 EXPLAIN 看看你的 SQL 到底是因为"没有合适的索引"还是"优化器抽风"导致的全表扫描,再对症下药!
注意:
自增主键:物理存储连续,全表扫描时磁盘的顺序 I/O 效率极高。
非自增主键:物理存储碎片化严重。如果表比较大,顺着逻辑链表去扫描全表,可能会导致大量的随机 I/O(因为相邻的逻辑页在物理磁盘上可能隔了十万八千里),这时候全表扫描的代价就会急剧升高。 - 建立完美的联合索引(最推荐)
-
5.避免频繁回表的覆盖索引场景
- 现象:查询结果需要访问许多非索引列。
- 原因:
- 如果索引列只覆盖了少量查询条件,MySQL 需要频繁"回表"获取数据。
- 全表扫描可能比频繁回表更快。
- 优化建议:
- 通过覆盖索引设计(
SELECT只取索引列)优化查询。
- 通过覆盖索引设计(
6、批量更新或删除
- 现象:批量更新或删除操作时索引失效可能更快。
- 原因:
- 使用索引可能需要频繁调整索引树结构,增加额外的开销。
- 如果影响的行数较多,MySQL 优化器可能选择全表扫描。
- 优化建议:
- 在批量操作时临时删除索引,操作完成后再重新创建索引。
结论:
使用索引并不总是最优解,需要根据具体的表结构,数据分布,查询语句和场景,选择合适的优化策略
可以通过explain来分析查询计划,验证是否使用了索引以及索引的效果