MySQL什么时候索引失效反而提升效率?

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 导致走了全表扫描且性能很差,可以尝试以下几种方案:

      1. 建立完美的联合索引(最推荐)
        确保你的索引能同时满足 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 条,既不用全表扫描,也不用额外排序。
      2. 避开 Bug 的 Trick(针对情况二)
        如果你不幸遇到了上述的优化器 Bug,可以通过"欺骗"优化器来强制它走正确的索引:
        方法一:使用 FORCE INDEX(你的索引名) 强制指定索引。
        方法二(更优雅):将 ORDER BY id 改写为 ORDER BY (id+0)。因为给字段加了数学运算,优化器会认为无法直接利用主键的有序性来偷懒,从而乖乖地去评估其他索引的成本,最终往往能选对索引。
      3. 拒绝 SELECT
        如果查询的字段过多,无法走"覆盖索引",数据库就必须频繁回表。在 LIMIT 较大时,回表的开销会急剧放大,导致优化器更容易做出错误的判断。尽量只查询需要的字段。
        你可以先用 EXPLAIN 看看你的 SQL 到底是因为"没有合适的索引"还是"优化器抽风"导致的全表扫描,再对症下药!

      注意:
      自增主键:物理存储连续,全表扫描时磁盘的顺序 I/O 效率极高。
      非自增主键:物理存储碎片化严重。如果表比较大,顺着逻辑链表去扫描全表,可能会导致大量的随机 I/O(因为相邻的逻辑页在物理磁盘上可能隔了十万八千里),这时候全表扫描的代价就会急剧升高。

5.避免频繁回表的覆盖索引场景

  • 现象:查询结果需要访问许多非索引列。
  • 原因:
    • 如果索引列只覆盖了少量查询条件,MySQL 需要频繁"回表"获取数据。
    • 全表扫描可能比频繁回表更快。
  • 优化建议:
    • 通过覆盖索引设计(SELECT 只取索引列)优化查询。

6、批量更新或删除

  • 现象:批量更新或删除操作时索引失效可能更快。
  • 原因:
    • 使用索引可能需要频繁调整索引树结构,增加额外的开销。
    • 如果影响的行数较多,MySQL 优化器可能选择全表扫描。
  • 优化建议:
    • 在批量操作时临时删除索引,操作完成后再重新创建索引。

结论:

使用索引并不总是最优解,需要根据具体的表结构,数据分布,查询语句和场景,选择合适的优化策略

可以通过explain来分析查询计划,验证是否使用了索引以及索引的效果

相关推荐
tiancaijiben8 分钟前
阿里云云备份(Cloud Backup)全量对接与使用指南
数据库·oracle
sulikey19 分钟前
数据库中等值连接与自然连接的区别。为什么不建议使用自然连接?
数据库·sql·mysql·等值连接·自然连接
IT策士30 分钟前
Redis 从入门到精通:分布式锁 —— 从 SETNX 到 Redlock
数据库·redis·分布式
云计算磊哥@32 分钟前
运维开发宝典027-MySQL03数据库的增删改查
运维·数据库·运维开发
李白的天不白34 分钟前
数据库的CEUD
数据库·sql·oracle
linux修理工1 小时前
kafka积压
数据库·分布式·kafka
i220818 Faiz Ul1 小时前
药店管理|基于springboot + vue药店管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·美食分享系统
不吃土豆的马铃薯1 小时前
C++ 正则表达式入门详解
linux·服务器·网络·数据库·c++·正则表达式
sulikey1 小时前
数据库系统概论 - 定义与查询 期末速成课笔记
数据库·笔记·期末考试·数据查询·期末速成·数据库系统概论·数据定义
nan madol1 小时前
PolarDB 分布式版(PolarDB-X)
数据库