ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL索引使用与优化:从原理到慢查询排查实战

MySQL索引使用与优化:从原理到慢查询排查实战 做后端开发这些年凡是带数据库的项目十有八九的慢查询最后都会落到同一个点上索引该建没建或者建了一堆但根本没真正生效。很多人一听到“MySQL 索引的使用”就以为是写几个 CREATE INDEX可实际工作里索引用得好不好直接决定同一套 SQL 在百万级数据量下是秒回还是卡死。这篇文章不讲虚的先带你理解索引的底层存储逻辑再把联合索引、最左前缀、索引失效、排序加速这些高频问题掰开揉碎最后用 EXPLAIN 完整演示一遍线上慢查询的排查过程。不管你是刚入门的 MySQL 新手还是被索引坑过不止一次的同学这十分钟都能换回不少实战经验。我个人很反感那种背八股式的索引教程上来就甩一堆“索引失效场景”也不讲为什么。所以下面每一节都会先解释原理再给实操结论并附上我踩过的坑和现在还在用的排查习惯。1. 索引到底在解决什么问题1.1 没有索引时MySQL 在干“全表扫描”这件事先想明白一个最基础的问题MySQL 在执行查询时默认动作是“从第一行开始把整张表的数据一块一块读出来逐条匹配 WHERE 条件”。这个过程叫全表扫描。表里只有几千条数据时无所谓但一旦到了百万级别哪怕每条记录只有 100 字节MySQL 也要读取几十上百 GB 的数据才能给你返回几个结果。生产环境里这种 SQL 跑一次就能把 IO 拖垮业务方还以为是数据库死锁了。索引的本质就是给数据建立一个“可跳过的检索结构”让 MySQL 不用遍历每一行而是直接锁定一个很小的数据范围。这里有个最经典的类比新华字典。如果所有汉字都按拼音顺序堆在一起你找“博客的博”就得从第一页翻起这就是全表扫描。而拼音检字表告诉你“bo”在第几页这就是索引。字典不可能把每个字都重新抄一份它只是额外维护了一份非常精简的“目录”也就是我们说的索引表。所以请记住第一句话索引是用额外的存储空间和写入成本换取查询时的效率。它不是白给的也不是越多越好。1.2 B树一层一层往下找IO 次数少得可怜MySQL 的 InnoDB 存储引擎选择的数据结构是 B树网上很多文章把它画得花里胡哨但核心就三点第一B树是一个多路平衡树节点会分裂和合并树的高度通常只有 2 到 4 层。就算一张表有几千万行只要索引键区分度足够从根节点到叶子节点只需要几次磁盘 IO。第二真正存储数据的是最底层的叶子节点。InnoDB 的插入操作永远发生在叶子节点并且叶子节点之间用指针相连形成有序链表这对范围查询和排序非常友好。你可以把它理解成“目录本身就带顺序”所以走索引拿到的一批数据天然就是有序的。第三非叶子节点只存索引键和指针不存完整行数据。这样一页 16KB 的 InnoDB 数据页能容纳成千上万个索引键大大降低了树的高度也就减少了磁盘读取次数。这也是 B树相比二叉树、哈希索引的核心优势。我见过不少同事把索引当成“万能加速器”建了索引就说 SQL 一定快。其实不对索引生效有一个前提你要查的数据确实能通过这个树形结构“缩小范围”。如果查询条件写得太发散MySQL 优化器评估后觉得走索引还没全表扫描划算照样会放弃索引。1.3 聚簇索引与二级索引两种索引的存储差异InnoDB 里默认主键索引是聚簇索引叶子节点直接存放整行数据。也就是说表数据本身就是按主键顺序组织的一棵 B树。这就引出一个重要推论InnoDB 表必须有主键没有显式主键时它会从非空唯一索引里挑一个当主键再不行就生成一个隐藏的 rowid 作为主键。而其他索引普通索引、唯一索引、联合索引都是二级索引也叫辅助索引。二级索引的叶子节点并不存完整行数据只存“索引列的值 对应的主键值”。所以走二级索引查询时通常会发生一次“回表”先用索引树定位到主键再拿主键去聚簇索引里取整行数据。简单说聚簇索引查一次就能拿到整行二级索引查两次。理解了这点你就能理解为什么“查询不需要回表”的覆盖索引能让性能起飞。当索引里已经包含了所有要查的字段时MySQL 连回表都省了直接在索引树里返回结果这就是 EXPLAIN 里的 Using index。2. 索引分类与联合索引选型2.1 主键索引、唯一索引、普通索引到底有什么区别日常建索引时最绕不开的就是这三种类型。我直接列个表类型是否允许重复是否允许 NULL一个表可建数量存储特点主键索引不允许不允许只能有 1 个聚簇索引叶子存整行唯一索引不允许允许可以有多个 NULL可有多个二级索引叶子存主键普通索引允许允许可有多个二级索引叶子存主键主键索引和唯一索引在查询性能上几乎没有差别因为它们定位到一个不重复的值时都很快。真正的差别在约束和数据完整性上。主键用来唯一标识一行记录所以非空且唯一唯一索引只是约束“业务上不能重复”比如用户表里的手机号字段可以建唯一索引但如果存在历史脏数据导致部分手机号为 NULL依然能建成功。在 InnoDB 中主键索引还承担物理组织的角色二级索引叶子节点存的是主键值。如果主键是自增整数插入永远追加在 B树末尾页分裂少如果主键是随机的 UUID插入时数据需要频繁移动页分裂就会很严重写入性能明显下降。这也是我不建议用 UUID 当主键的核心原因。唯一索引还有一个容易忽略的坑它对写入会做唯一性检查所以插入和更新时要额外读一次索引做判重。普通索引没有这个检查写入略快。仅从性能出发业务并不要求唯一约束的字段不要顺手加 UNIQUE。2.2 单列索引和联合索引怎么取舍单列索引就是只用一个字段建索引联合索引是用多个字段一起建索引。很多人习惯给每个出现在 WHERE 里的字段单独建一个索引觉得这样最保险这是典型错误。MySQL 在一个查询里虽然可以用多个单列索引做 index merge但优化器要额外合并结果集多数时候效果不如一个设计合理的联合索引。联合索引的核心价值不是“多个单列索引的叠加”而是让索引列之间配合扫描。比如索引 (a, b)MySQL 可以先按 a 锁定一个范围再在这个范围内按 b 精确过滤。这一步叫索引下推它把原本要在回表后用 WHERE 过滤的动作提前到了索引层减少了回表次数。所以取舍原则很明确高频查询里有多个条件同时出现时优先考虑联合索引只在 WHERE 里单独出现的字段才考虑单列索引。不要一上来就建一堆单列索引后人维护起来崩溃优化器也未必领情。2.3 最左前缀原则联合索引的“游戏规则”联合索引 (a, b, c) 本质上先按 a 排序a 相同再按 b 排序b 相同再按 c 排序。所以 MySQL 只能从最左边的 a 开始连续匹配。查询条件里如果完全不带 a这个索引基本废掉如果只带 a 和 c 而跳过 b那么只有 a 能走索引c 只能在索引返回的数据里做过滤无法再利用树结构精确查找。这个规则是新手最大的认知分水岭。“最左前缀”并不是说 SQL 的写法上 a 必须写在 b 前面而是说查询条件里必须包含索引的最左列并且各个条件的等值关系要与索引构建顺序匹配。比如索引是 (user_id, status, create_time)那么 WHERE statuspaid AND user_id123 同样走索引因为优化器会先取出 user_id123 的区间再在这个区间里挑 status。最左前缀还牵涉到“范围列”问题。如果查询里出现了 status paid 这类范围条件那 create_time 再放进去也是白搭因为 B树在一个范围区间内无法继续对后面的列做定位。换句话说联合索引的“等值字段”可以一直复用一旦遇到范围条件后面的索引列就失效了。2.4 经典问题where 条件 a and b索引应该怎么建这是热搜里非常高频的问题也是我之前在团队里讲过无数遍的场景。假设表结构大概是CREATE TABLE order_info ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, status tinyint NOT NULL, create_time datetime NOT NULL, pay_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB;业务里最常见的 SQL 是SELECT * FROM order_info WHERE user_id 123 AND status 1 ORDER BY create_time DESC LIMIT 20;这种“等值条件加排序”的组合该怎么建联合索引我的习惯分三步。第一步把所有等值条件字段找出来user_id 和 status这两个都是等值匹配它们的先后顺序理论上不影响索引是否能命中因为优化器会自己调整位置。但为了和未来扩展对齐一般把区分度高的放前面或者把经常单独查询的字段放前面。第二步看 ORDER BY 字段。如果期望避免 filesort需要把 create_time 也加进索引并且它的位置要放在等值字段之后。因为只有前面的字段都确定了等值后面的字段才能利用 B树的天然有序性完成排序。第三步得出推荐索引联合索引 (user_id, status, create_time)或者 (status, user_id, create_time) 也可以但要考虑是否另一套组合能覆盖更多查询。如果业务里还有单独的 WHERE user_id ? 查询那么 (user_id, status, create_time) 就是最优解因为最左列单独也能用。如果单独按 status 查询也很高频那 (status, user_id, create_time) 可能更好但代价是 user_id 的单独查询没法走到联合索引。一句话总结这个问题的答案先把等值条件按区分度排序再把排序字段放在等值条件的后面最后用最左前缀校验当前索引能不能服务现有查询。不要迷信“哪个字段经常出现就放第一个”要结合 ORDER BY、GROUP BY、范围查询和覆盖列综合判断。3. 创建索引的实操方法与空间成本3.1 建索引的三种姿势我平时建索引用的方式大概就三种各有各自的使用场景。建表时直接定义适合表结构还没上线时一次性把主键、唯一键和普通索引都写清楚CREATE TABLE test_user ( id bigint NOT NULL AUTO_INCREMENT, mobile varchar(20) DEFAULT NULL, email varchar(100) DEFAULT NULL, created_at datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_mobile (mobile), KEY idx_created_at (created_at) ) ENGINEInnoDB;表已经存在需要补充索引时用 ALTER TABLE适合加索引时可以指定算法和锁策略ALTER TABLE test_user ADD INDEX idx_email (email); ALTER TABLE test_user ADD UNIQUE INDEX uk_mobile (mobile);还有一种独立的 CREATE INDEX 语法语法上等价于 ALTER TABLE ADD INDEX但语义上更侧重“创建”我习惯在脚本里用它方便阅读CREATE INDEX idx_email ON test_user(email);删除索引时用 DROP INDEX或者 ALTER TABLE DROP INDEX。注意删除主键索引要谨慎因为 InnoDB 主键不仅承担索引职责还负责表的物理编排。3.2 查看索引与执行计划索引建完如何确认它真的存在并且被使用两条命令必不可少。第一条是查看表上的索引信息SHOW INDEX FROM test_user;它会输出索引名、字段顺序、基数Cardinality、是否是唯一索引等信息。第二条是查看 SQL 的执行计划也就是 EXPLAINEXPLAIN SELECT * FROM test_user WHERE email ab.com;我最关注的是 type、key、rows、Extra 这四列。type 从好到差大致是 system、const、eq_ref、ref、range、index、ALL看到 ALL 基本就是全表扫描了。key 表示实际用到的索引rows 表示预估扫描的行数Extra 里的 Using index 是最好情况Using filesort 和 Using temporary 则说明排序或分组没走索引需要优化。需要注意的是EXPLAIN 是预估结果不是真实执行结果。分析热点问题时我会再加一条 EXPLAIN ANALYZEMySQL 8.0看真实耗时和实际扫描行数。3.3 索引表空间索引到底吃了多少磁盘很多同学建索引时完全不算空间账直到磁盘报警才开始查。在 InnoDB 中索引数据和表数据都存放在表空间里。默认情况下如果开启了独立表空间innodb_file_per_tableON每个表对应一个 .ibd 文件索引和表数据共用这个文件如果用的是共享表空间索引数据就混在共享表空间中想释放也只能整体回收。精确算索引占多大可以查 information_schema 里的统计信息但那个更新是采样式的并不精确。最稳的办法是直接看操作系统层面的 ibd 文件大小ls -lh /var/lib/mysql/yourdb/test_user.ibd这个文件大小包含了所有索引和数据。想单独评估索引占比可以通过索引键长度估算一个二级索引记录大约等于“索引列长度 主键长度 一些头信息”。假设主键是 8 字节 bigintemail 是 varchar(100) 实际平均 30 字节每条索引记录大约 50 字节左右一页 16KB 大约能放 300 条1000 万行数据就需要 3 万多页将近 600MB这还不算 B树内部节点的开销。所以我常说索引不是免费的午餐建索引前先想想这条字段到底有多长。另外删除索引后表空间并不会自动缩水除非你用 OPTIMIZE TABLE 或者 ALTER TABLE ... FORCE 重建表。但这类操作在线上会锁表或者占用大量 IO要避开业务高峰。3.4 命名规范与维护纪律索引命名这件事看似不重要真到了排查问题的时候一个不规范的索引名能把人气死。我一般用这么一套规则主键索引PRIMARY没得选唯一索引uk_字段名_字段名普通索引idx_字段名_字段名联合索引idx_字段名_字段名_字段名字段名之间用下划线分隔这套规则的好处是看到索引名就知道索引覆盖了哪几个字段不用每次 SHOW INDEX 去猜。另外一个维护纪律是每条索引都要有存在的理由。我见过一个订单表建了十几个索引其中好几个的字段前缀完全一致只差一个尾部字段这明显就是不同同学“各加各的”结果。索引越多INSERT、UPDATE、DELETE 时维护成本越高Buffer Pool 里被索引缓存占掉的内存也越多最终影响整体性能。给已有表新增索引前我会先用 sys.schema_unused_indexes 查一下哪些索引自打建好就没被用过先把没用的清掉再谈新增。4. 哪些场景会让索引失效4.1 八种典型的失效场景索引失效是面试高频也是线上事故高发点。失效不等于删除索引而是优化器评估后认为这个索引帮不上忙选择了更笨的办法。我整理出八种最常见的情况每一项都直接给坑和解决办法。第一对索引列使用函数。比如 WHERE DATE(create_time) 2024-06-01MySQL 对 create_time 套了一层函数后B树的有序性就被打破了只能全表扫描。解决办法是先算好范围WHERE create_time 2024-06-01 00:00:00 AND create_time 2024-06-02 00:00:00。第二隐式类型转换。如果 phone 字段是 varchar查询写 WHERE phone 13800138000MySQL 会把字符串列转换为数字导致索引失效。正确写法是给数字加引号。反过来也一样数字列和字符串比较也可能出问题。第三LIKE 前置通配符。WHERE name LIKE %张因为开头不确定B树无法定位起点索引直接失效。解决办法是查线上数据时尽量避免这种写法必要的话考虑全文索引或搜索引擎类方案。后匹配的 name LIKE 张% 不受影响。第四OR 条件里有非索引列。WHERE id 1 OR name abc即使 id 有索引name 没索引MySQL 也没法用两个区间直接合并干脆全表扫描。解决办法是把 OR 两边都改成有索引的字段或者拆成两条 SQL 用 UNION ALL 合并。第五联合索引不满足最左前缀。索引是 (a, b, c)查询只写了 b 1索引从第一条路就断了。第六范围条件后面的索引列。WHERE a 1 AND b 5 AND c 3索引 (a, b, c) 最多用到 a 和 bc 只能回表过滤。所以联合索引字段顺序要仔细排等值字段放前范围字段放后如果范围字段本身不是核心条件甚至可以不放进去。第七使用 ! 或 。这个要分情况如果表的区分度很高并且访问的数据量很小优化器偶尔会走索引但很多人期望它每次走索引实际却变成全表扫描。区分度低的字段比如 status 只有 0、1、2 三个值无论怎么建索引优化器都会选择扫描全表因为走索引的代价反而更高。第八IS NULL 和 IS NOT NULL。InnoDB 二级索引对 NULL 的处理比较特殊普通索引中 NULL 值可以被多个记录使用但当 WHERE name IS NULL 时不是所有情况都能用到索引。如果确实经常要查“某字段为空”可以考虑给该字段设定默认空字符串或 0再建普通索引。4.2 用 EXPLAIN 定位索引失效遇到慢查询我的第一反应就是跑 EXPLAIN而不是靠肉眼猜。给你看一个实际例子。假设订单表有一个索引 idx_user_status(user_id, status)现在执行EXPLAIN SELECT * FROM order_info WHERE status 1 AND user_id 123;结果里 type 为 refkey 为 idx_user_status也就是走了索引。但如果把条件改成EXPLAIN SELECT * FROM order_info WHERE status 1;因为 WHERE 没包含最左列 user_id索引失效这时 type 会变成 ALLrows 直接变成全表行数。这就是最左前缀的验证现场。排查索引失效时我习惯把 SQL 里的条件逐个用 EXPLAIN 跑一遍每加一个条件看一次执行计划。通过改变条件观察 key 和 rows 的变化就能精确找出是哪个字段“打断”了索引链路。这种排查法比我对着索引定义脑补要快得多。4.3 一个线上慢查询的修复过程有次线上报警某个列表接口响应从 50ms 涨到了 5 秒查慢查询日志发现典型 SQLSELECT id, user_id, title, create_time FROM article WHERE category_id 10 ORDER BY create_time DESC LIMIT 20;表里当时的索引是单列索引 idx_category(category_id)。EXPLAIN 一看type 是 refkey 也用了 idx_category但 Extra 里出现了 Using filesort。也就是说系统先用 category_id 筛出了几十万行再对 create_time 做了一次磁盘级排序最后才取 20 条慢得理所当然。修复方案就是把排序字段也收进索引ALTER TABLE article ADD INDEX idx_category_create_time (category_id, create_time DESC);MySQL 8.0 里 DESC 后缀能直接建降序索引8.0 之前的版本加不加 DESC 其实没有影响因为旧版本无法按降序存储优化器会反向扫描。这个索引一加EXPLAIN 的 Extra 里 Using filesort 消失了变成了 Using index condition 和 Using index。接口耗时直接降回 60ms 左右。这个案例给我最大的教训不是“建个联合索引就行”而是“发现问题时先看 Extra 列的 filesort 提示再反推索引字段顺序”。5. 索引对排序和事务的影响5.1 用索引干掉 filesortMySQL 的排序分两种一种是直接从索引里按顺序读取根本不需要额外的排序动作另一种是因为查询走不上索引顺序MySQL 只能把结果先装载到内存或磁盘上排序也就是 filesort。filesort 不一定真的落到磁盘但即使只在内存中排也是白消耗 CPU 和临时空间。从执行计划里可以直观判断Extra 里出现 Using filesort基本就意味着排序没有利用上索引。我见过很多面试题问“MySQL 里怎么优化 ORDER BY”答案绝不是“排序本身很快”而是“让排序字段和 WHERE 条件字段组成联合索引并满足最左前缀”。比如 WHERE a 1 ORDER BY b如果索引是 (a, b)那么 MySQL 在 a1 这个区间里读到的数据就已经按 b 排好了LIMIT 20 只需要取前 20 条就结束效率极高。这里要强调一点DESC 索引并不是万能的只有热点查询里明确要求降序并且与 ORDER BY 的方向完全一致才需要建 DESC 索引。MySQL 8.0 之前所有索引都是升序存储查降序时优化器反向扫描即可性能也不差。8.0 之后如果确认反向扫描有压力显式指定 DESC 是更可控的做法。5.2 索引、锁与事务隔离索引失效不只是变慢那么简单索引失效在事务场景下还有一个容易被忽略的连锁反应锁范围失控。InnoDB 在可重复读隔离级别下有间隙锁机制目的是防止幻读。如果 UPDATE ... WHERE 条件的字段没有索引InnoDB 只能全表扫描定位要更新的行这意味着它可能需要给整张表的所有记录和间隙加锁。虽然 MySQL 有一定优化会减少锁的数量但风险非常大。而有索引时锁直接落在索引命中的记录和对应间隙上范围精确得多。我之前处理过一个故障某运营后台按用户状态批量更新一条数据因为那个状态字段没有索引执行时不少其他请求被堵在锁等待上。当时的解决办法并不是改事务逻辑而是给状态字段补了一个普通索引更新 SQL 本身没动锁竞争立即缓解。所以处理事务型慢查询时别只盯着查询耗时也要想想“这条 SQL 会锁住多少行”。索引越不精确锁的波及面越大甚至可能引发连锁死锁。事务与索引之间的第二个影响是 MVCC。二级索引上的老版本记录通过 undo log 维护聚簇索引里保留了隐藏的事务 ID 和回滚指针。查询走索引可以减少扫描的可见性判断成本这也是为什么高频事务表更需要把索引设计做扎实。5.3 主键索引和唯一索引的区别面试和工作都要会用主键索引和唯一索引是 MySQL 面试中出现频率最高的两个概念这里把它们的区别说得再透一点。从约束层面看主键索引不可以为 NULL且一张表只能有一个唯一索引可以为 NULL也允许存在多个 NULL 值因为 InnoDB 对唯一索引的 NULL 判定是“NULL 不等于 NULL”。从存储层面看InnoDB 中主键索引是聚簇索引叶子节点存储整行数据唯一索引是二级索引叶子节点存储索引列和主键值。这就意味着直接通过主键回表是零成本而通过唯一索引查询还需要一次回表操作。从写入性能看主键自增是最高效的因为新记录总追加在 B树右侧页分裂极少而 UUID 主键会导致 B树中间频繁分裂。唯一索引的写入则多一步唯一性校验普通索引没有这一步所以如果某个字段只是业务上需要快速检索并不需要唯一约束建普通索引即可没必要硬上 UNIQUE。还有一点非常关键InnoDB 中二级索引必然包含主键值所以主键越短所有二级索引的叶子节点就越小占用的表空间和 IO 就越少。这也是我反复提醒团队“不要把超长字符串当主键”的根本原因。6. 索引调优与常见问题速查6.1 冗余索引识别与清理索引调优第一步不是增加索引而是清理冗余索引。什么叫冗余最典型的是已经有联合索引 (a, b)同时又建了单列索引 (a)。因为联合索引的最左列就是 a单列索引 (a) 完全能被 (a, b) 替代除非 (a) 还能独立支撑另一类查询且联合索引的字段顺序会导致排序不符合要求。另一种容易混淆的情况已有 (a, b)又建了 (b, a)这两个并不冗余因为它们的最左列不同服务的是不同查询需求。已有 (a, b) 和 (a, b, c)则后者在多数场景下可以替代前者因为 (a, b, c) 覆盖了 (a, b) 的能力还多了 c。清理冗余索引时MySQL 8.0 可以直接查 sys 库SELECT * FROM sys.schema_redundant_indexes;MySQL 5.7 也有该方法但部分版本需要初始化 sys 库。删除索引前务必确认没有业务 SQL 依赖它最稳妥的是先在测试环境把旧索引隐藏INVISIBLE观察一段时间确认无影响后再 DROP。6.2 索引相关的常见问题速查表这些年我在社区和团队里收集了不少索引相关的典型问题整理成一张速查表方便各位直接对照现象可能原因快速排查查询突然变慢几十倍数据量增长导致扫描行数上升EXPLAIN 看 rows 和 type明明建了索引但没走最左前缀不满足 / 隐式转换 / LIKE 前置通配检查 WHERE 条件是否触碰索引列Extra 出现 Using filesort排序字段不在索引中重建联合索引把排序字段加进去Extra 出现 Using temporaryGROUP BY 字段顺序与索引不一致调整索引字段或 SQL 逻辑更新语句锁等待严重WHERE 条件没有索引导致锁范围过大给 WHERE 字段加索引索引太多导致写入慢二级索引数量过多写一条索引要维护多次清理冗余索引合并联合索引表空间暴涨二级索引过大或历史版本数据累积查看 ibd 文件大小评估索引必要性查询返回大量重复索引覆盖索引没设计好尝试用覆盖索引减少回表顺带提一句像“MySQL SSL 连接错误”“Docker 安装 MySQL 失败”这类问题基本和索引没有直接关系排查时先查网络层、账号权限和配置文件不要一上来就往索引上套。6.3 关于索引数量和维护成本的一点经验索引的维护成本平时不容易量化但一旦遇到大批量数据导入或高并发写入就会立刻暴露。每插入一行InnoDB 除了修改聚簇索引还要同步修改该表所有二级索引。五张二级索引就是五条索引链的写入任何一条索引里的页分裂都可能拖慢整体插入速度。所以我的经验是单表索引数量尽量控制在 5 个以内核心高频写表的索引更是要反复审视。尤其不要出现“同一个前缀字段拆成三四个索引”的奇观。如果要支持复杂的多条件筛选优先考虑用联合索引覆盖最核心的两三种查询组合剩下低频需求就让全表扫描自己扛或者交给统计报表临时处理。索引调优也不是一次性的工作。业务数据量在涨索引基数在变优化器的判断依据也在变。我每个月会花小半天时间把慢查询日志里的 Top SQL 拉出来重新用 EXPLAIN 过一遍专门检查有没有索引没“跟上数据量增长”。这比临时抱佛脚地加索引要省心得多。回到开头那个问题where 条件 a and b 应该怎么建索引。你已经知道答案了先看等值条件再看排序字段最后用最左前缀校验。但比这个具体答案更重要的是掌握背后的原理。我自己刚接触 MySQL 时也以为索引是“银弹”后来被线上故障反复教育才慢慢明白索引其实是一笔带利息的投资。你会用它它会帮你省下成百上千倍的时间你不会用它它就悄悄吃掉磁盘、拖慢写入、扩大锁范围。这篇内容就是一个完整的排查工具箱以后你再碰到慢查询不妨从索引开始查起。
返回列表