资讯动态

MySQL索引调优实战:B+树与EXPLAIN慢SQL优化指南

发布时间:2026/9/8 6:14:30 来源:尧图企业网站定制
MySQL 索引调优是数据库面试中提问频率最高的方向之一也是线上慢 SQL 治理时第一个要检查的环节。很多开发者在建表时随手加几个索引遇到查询变慢后又继续加索引最后索引数量越来越多写入越来越慢查询也没有明显变好。这种情况通常不是因为 MySQL 本身的问题而是没有把索引的存储结构、执行计划、SQL 写法放在一起看。这篇文章以 MySQL 8.0 为准从 B 树索引的基本工作原理讲起用一套可复现的 SQL 样例带你跑通EXPLAIN理解最左前缀、覆盖索引、索引失效、排序和分组优化再回到面试题里常见的问题。阅读之前如果还没安装 MySQL可以先准备一个本地实例如果已经在使用 MySQL直接创建测试库即可。学完以后你应该能独立分析一条慢 SQL 的EXPLAIN输出判断该加什么索引也能解释清楚为什么有些索引建了却用不上。1. 先理解 MySQL 索引为什么选择 B 树1.1 全表扫描的代价和索引的价值一张只有几千行的表全表扫描通常不会造成明显问题。可当表增长到几百万行、上千万行时每次查询都从第一行扫到最后一行磁盘读取次数会接近表的数据页总数响应时间就会迅速上升。索引的本质是提供一种“不需要看完整张表也能定位到目标数据”的路径。MySQL 的 InnoDB 存储引擎默认使用 B 树来组织索引。B 树是一种多路平衡查找树它的内部节点只保存索引键值不保存完整行数据所以一个 16KB 的数据页可以容纳更多键值树的层数更矮。对于千万行甚至上亿行的数据常见场景下 B 树的层数可以保持在三四层左右查询时从根节点到叶子节点只需要少数几次磁盘 IO。另一个关键点是 B 树的叶子节点之间是有序链表。这个结构非常适合范围查询找到起点后可以沿着链表向后顺序扫描不需要每次都回到根节点重新搜索。这也是 B 树在数据库索引场景中优于哈希索引的重要原因。1.2 聚簇索引、二级索引和回表InnoDB 表数据本身就是按聚簇索引组织的。聚簇索引的键通常是主键叶子节点保存的是完整行记录。如果建表时没有显式指定主键InnoDB 会选择第一个非空唯一索引作为聚簇索引如果也没有唯一索引则会生成一个隐藏的rowid作为聚簇索引键。二级索引的叶子节点不保存完整行数据只保存索引列的值和对应主键值。通过二级索引查到主键后再根据主键到聚簇索引中取完整行这个过程叫做回表。回表会增加一次主键索引查找虽然通常比全表扫描快很多但在海量数据下也不是免费的。因此当 SELECT 需要的列都已经包含在某一个二级索引中时就可以直接使用索引数据不再回表这种索引也叫覆盖索引。索引类型叶子节点内容典型特点常见用途聚簇索引完整行记录表数据与索引数据一体主键查找、范围扫描二级索引索引列值 主键值查询可能需要回表普通查询加速复合索引多个列的值 主键值受最左前缀规则约束多条件查询、排序优化覆盖索引查询所需列都在索引中不需要回表高频查询优化1.3 索引类型速查InnoDB 中常见的索引类型包括主键索引、唯一索引、普通索引、复合索引和全文索引。索引类型是否允许重复是否允许 NULL特点主键索引不允许不允许聚簇索引一张表只能有一个唯一索引不允许通常允许保证列值唯一也可加速查询普通索引允许允许只加速查询不约束数据复合索引跟索引定义有关跟列是否可空有关多个列联合排序和查找全文索引允许允许用于全文检索CHAR/VARCHAR/TEXT 可建索引不是越多越好。每个二级索引在写入时都要额外维护一份 B 树结构数据插入、更新、删除的成本都会上升。真正的调优目标是用尽量少的索引覆盖尽量多的高频查询。2. 搭建 MySQL 8.0 测试环境数据尽量接近真实2.1 Docker 启动和连接学习环境可以直接使用 Docker 启动 MySQL 8.0避免污染本机已有数据库。下面命令会在本机3306端口启动一个名为mysql8-test的容器密码只是测试用途docker run --name mysql8-test \ -e MYSQL_ROOT_PASSWORDroot123 \ -p 3306:3306 \ -d mysql:8.0启动后连接mysql -h127.0.0.1 -P3306 -uroot -p输入密码后进入 MySQL 命令行。如果端口被占用可以把宿主机端口改成3307后面连接时也要对应修改docker run --name mysql8-test \ -e MYSQL_ROOT_PASSWORDroot123 \ -p 3307:3306 \ -d mysql:8.0 mysql -h127.0.0.1 -P3307 -uroot -p生产环境不建议直接用 root 远程连接也不建议在命令行里长期暴露明文密码。学习环境可以使用 root但生产环境必须创建独立账号按最小权限原则授权。2.2 建表、插入 10 万行测试数据索引调优必须用有一定数据量的表来验证。几千行数据里全表扫描也许比索引还快容易得出错误结论。下面创建一张员工测试表包含主键、工号唯一索引和(dept_id, create_time)复合索引CREATE DATABASE IF NOT EXISTS index_lab DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; USE index_lab; CREATE TABLE t_employee ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, emp_no VARCHAR(32) NOT NULL COMMENT 工号, name VARCHAR(64) NOT NULL COMMENT 姓名, dept_id INT NOT NULL COMMENT 部门ID, salary DECIMAL(12,2) NOT NULL COMMENT 薪资, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_emp_no (emp_no), KEY idx_dept_create (dept_id, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT员工测试表;接着插入 10 万行测试数据。为了后面验证最左前缀和范围查询这里让dept_id分布在 1 到 100create_time分布在 2024 年的一年内TRUNCATE TABLE t_employee; INSERT INTO t_employee (emp_no, name, dept_id, salary, create_time) WITH RECURSIVE seq AS ( SELECT 1 AS n UNION ALL SELECT n 1 FROM seq WHERE n 100000 ) SELECT CONCAT(E, LPAD(n, 8, 0)), CONCAT(user_, n), n % 100 1, ROUND((n * 17 % 20000) 5000, 2), DATE_ADD(DATE(2024-01-01), INTERVAL (n % 365) DAY) FROM seq;这段 SQL 使用了 MySQL 8.0 的递归 CTE生成 1 到 100000 的序列。LPAD把数字补齐成 8 位保证工号唯一。n % 100 1让部门分布在 1 到 100n % 365让日期分布在一整年内。如果重跑插入先执行TRUNCATE否则uk_emp_no会触发唯一键冲突。2.3 检查数据量和统计信息插入完成后先确认数据行数SELECT COUNT(*) FROM t_employee;正常结果应该是 100000。随后执行统计信息更新ANALYZE TABLE t_employee;ANALYZE TABLE会更新索引基数等统计信息帮助优化器更准确地估算rows和filtered。如果长期不更新统计信息即使索引存在优化器也可能因为统计信息过期而选择全表扫描。3. 用 EXPLAIN 读懂执行计划3.1 一条带复合索引的查询示例EXPLAIN是 MySQL 提供的执行计划分析工具。在一条 SELECT 前面加EXPLAIN不会真正执行查询而是展示优化器准备怎么查。先看一个使用了复合索引的例子EXPLAIN SELECT id, name, dept_id, salary FROM t_employee WHERE dept_id 10 AND create_time 2024-03-01;执行结果类似--------------------------------------------------------------------------------------------------------------------------------------- | id | select_type| table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | --------------------------------------------------------------------------------------------------------------------------------------- | 1 | SIMPLE | t_employee | NULL | range | idx_dept_create | idx_dept_create | 9 | NULL | 1000 | 100.00 | Using index condition | ---------------------------------------------------------------------------------------------------------------------------------------重点看几列字段含义当前示例说明type访问类型range表示范围扫描possible_keys可能用到的索引优化器考虑了idx_dept_createkey实际选用的索引确实用了idx_dept_createkey_len使用的索引字节长度表示用到了复合索引的两列rows预计扫描行数1000 是估算值不是精确值filtered过滤后剩余比例100 表示索引过滤后基本都能用Extra附加信息Using index condition表示用到了索引条件下推这里key_len为 9是因为dept_id是INT NOT NULL占用 4 字节create_time是DATETIME NOT NULL占用 5 字节合起来 9 字节。具体值会受到字段类型、是否可空、字符集影响实际项目以当前版本结果为准。注意EXPLAIN给出的是优化器估算值不是实际执行消耗。如果版本支持 MySQL 8.0.18 以上可以用EXPLAIN ANALYZE拿到实际执行时间和真实行数但生产环境执行前要确认查询只读且风险可控。3.2 type 字段决定访问方式type是执行计划里最值得先看的字段它决定了数据访问的效率。从好到差常见顺序大致如下type含义典型场景system表中只有一行系统表或极限情况下const主键或唯一索引等值查询WHERE id 1eq_ref联表时被驱动表使用主键或唯一索引JOIN等值连接ref普通索引等值查询WHERE dept_id 10range索引范围扫描BETWEEN、、、INindex遍历二级索引覆盖索引或需要扫描整棵索引树ALL全表扫描没有走到索引看到ALL时先不要急着下结论。如果表很小全表扫描可能是最优解只有当表很大、查询条件又强而执行计划仍然选择ALL时才说明索引或 SQL 写法存在问题。3.3 Extra 字段里的危险信号Extra字段中包含优化器执行细节常见值如下Extra 值含义处理方向Using index覆盖索引不需要回表很好保持Using index condition使用索引条件下推通常不错但可能还要回表Using where存储引擎返回后MySQL 服务层再过滤检查过滤条件是否能下沉到索引Using filesort额外的排序操作尝试用索引顺序消除排序Using temporary使用临时表常见于 GROUP BY、DISTINCT、子查询Backward index scan从后往前扫描索引常用于降序排序通常可接受Using filesort不是一定代表磁盘文件排序它也可能在内存中完成但它说明 MySQL 没有用到索引的有序性需要额外排序。Using temporary往往代价更高尤其是大分组查询要重点关注。4. 六类 SQL 调优场景和索引写法4.1 最左前缀复合索引的顺序不能乱复合索引idx_dept_create (dept_id, create_time)的排序规则是先按dept_id排序dept_id相同再按create_time排序。这种结构决定了查询条件必须从最左列开始才能充分利用索引。看下面三条 SQL-- 可以使用索引typeref EXPLAIN SELECT * FROM t_employee WHERE dept_id 10; -- 可以使用索引typerange EXPLAIN SELECT * FROM t_employee WHERE dept_id 10 AND create_time 2024-03-01; -- 单独使用 create_time 条件通常无法走这个复合索引 EXPLAIN SELECT * FROM t_employee WHERE create_time 2024-03-01;第一条 SQL 只要约束dept_id可以用 B 树先定位到具体部门然后在该范围内顺序读取。第二条 SQL 在部门内部追加时间范围索引依然能精确定位起点。第三条 SQL 跳过了第一列dept_id直接按第二列create_time查找关联的叶子节点顺序并不保证create_time全局有序所以通常很难走复合索引。MySQL 8.0 在某些低基数场景可能使用 Index Skip Scan但不要把它当成稳定依赖。这里要区分一个概念最左前缀说的是“索引列顺序”不是 SQL 关键字里的书写顺序。优化器在大部分情况下可以调整等值条件的顺序但索引本身的列顺序是固定的。SQL 条件能用到idx_dept_create吗原因WHERE dept_id 10能使用最左列WHERE dept_id 10 AND create_time ...能第一列等值第二列范围WHERE create_time ...通常不能跳过最左列WHERE dept_id IN (10,20) AND create_time ...能但注意扫描范围放大第一列是一个范围WHERE create_time ... AND dept_id 10通常能优化器会按索引顺序调整等值条件4.2 索引列上做函数运算范围查询代替函数表达式在索引列上使用函数是索引失效的高频原因。例如想查 2024 年 3 月 1 日当天创建的数据EXPLAIN SELECT * FROM t_employee WHERE DATE(create_time) 2024-03-01;由于DATE(create_time)改变了原始列的值B 树中存储的是create_time原值无法直接利用排序结构快速定位。更推荐的写法是范围查询EXPLAIN SELECT * FROM t_employee WHERE create_time 2024-03-01 00:00:00 AND create_time 2024-03-02 00:00:00;如果要单独验证create_time列可以补充一个单列索引ALTER TABLE t_employee ADD INDEX idx_create_time (create_time), ALGORITHMINPLACE, LOCKNONE;MySQL 8.0 中给已有表添加二级索引通常可以使用ALGORITHMINPLACE, LOCKNONE但不同操作支持程度不同落地前要确认版本和 DDL 类型。实际项目中日期时间字段尽量用和形成半开区间这样既避免了函数也能让优化器更自由地选择索引。4.3 隐式类型转换字符串列就要用字符串比较表里emp_no是VARCHAR(32)类型查询时如果写成了数字容易引发隐式类型转换-- 推荐字符串参数能和索引列类型匹配 EXPLAIN SELECT * FROM t_employee WHERE emp_no E00012345; -- 不推荐容易发生隐式类型转换 EXPLAIN SELECT * FROM t_employee WHERE emp_no 12345;字符串列和数字常量比较时MySQL 需要把其中一侧转换成另一种类型。一旦索引列被迫参与转换原本连续的字符串排序顺序就可能失效导致无法高效使用索引。同类问题还常见于phone、card_no这类本身是字符串但保存纯数字的字段。排查时可以通过SHOW WARNINGS查看优化器是否做了隐式转换EXPLAIN SELECT * FROM t_employee WHERE emp_no 12345; SHOW WARNINGS;SHOW WARNINGS的输出会包含一些转换信息。最常见的解决方案是让接口层或 SQL 参数类型和列类型保持一致。4.4 LIKE、OR、IN 和范围查询不是所有写法都适合索引模糊查询是另一个典型场景。-- 前缀匹配通常能走索引 EXPLAIN SELECT * FROM t_employee WHERE emp_no LIKE E001%; -- 后缀匹配索引帮助有限 EXPLAIN SELECT * FROM t_employee WHERE emp_no LIKE %001;LIKE E001%等价于在E001这个前缀范围内查找B 树的有序性可以帮忙。LIKE %001需要知道任意长度的前缀无法直接定位起始位置通常只能做全表或全索引扫描。如果业务必须做后缀搜索可以考虑存储反排字段、全文索引或引入专门的搜索中间件而不是硬扛 SQL。OR的情况需要特别注意。如果 OR 两边条件都能走索引优化器可能选择 Index Merge如果有一边没有合适索引很可能退化成全表扫描。-- dept_id 有复合索引但 name 没有单独索引可能需要全表扫描 EXPLAIN SELECT * FROM t_employee WHERE dept_id 10 OR name user_1;IN列表通常可以走range扫描但列表过长时扫描范围也会变大。对于大量离散值有时拆成多次等值查询再合并结果可能比一个超大IN列表更稳定。4.5 覆盖索引和回表少拿列就能少一次磁盘读取回表并不是每次都必须避免但对高频查询来说能用覆盖索引会显著减少随机 IO。下面两条 SQL 的差别很值得体会-- 查询列都在复合索引 idx_dept_create 中Extra 可能显示 Using index EXPLAIN SELECT dept_id, create_time FROM t_employee WHERE dept_id 10; -- 需要 name、salary 等不在索引中的列必须回表取完整记录 EXPLAIN SELECT * FROM t_employee WHERE dept_id 10;覆盖索引的本质是“查询的列已经包含在索引中”所以 SELECT 列表只放必要字段不要一上来就SELECT *。这既减少了网络传输也增加了覆盖索引的可能性。不过覆盖索引并不是白送的复合索引列越多写入维护成本越高需要在查询收益和写入成本之间平衡。4.6 ORDER BY 和 GROUP BY索引还能消除临时排序排序优化的关键是让索引顺序匹配ORDER BY顺序。-- 用了 dept_id 等值 create_time 排序复合索引天然有序可以避免 filesort EXPLAIN SELECT id, dept_id, create_time FROM t_employee WHERE dept_id 10 ORDER BY create_time DESC;如果只查询id, dept_id, create_time这些列都在idx_dept_create中并且WHERE dept_id 10先定位到部门子树ORDER BY create_time又符合索引内部排序就可能出现Backward index scan或Using index而不是Using filesort。GROUP BY也一样。如果分组列和索引顺序不一致MySQL 可能创建临时表。-- dept_id 是复合索引第一列这个分组通常可以利用索引 EXPLAIN SELECT dept_id, COUNT(*) FROM t_employee GROUP BY dept_id;MySQL 8.0 支持降序索引可以对频繁出现的ORDER BY a DESC, b ASC做针对性设计。但引入降序索引前必须确认优化器真的会使用并且对写入和维护成本做评估。5. 面试高频问题把原理讲清楚5.1 为什么 InnoDB 用 B 树而不是 B 树或哈希索引面试里最常见的追问是为什么 MySQL 索引不选哈希不选普通 B 树偏偏用 B 树。哈希索引的优点是等值查询速度极快理论上是 O(1)。但它的致命弱点是无法按区间顺序扫描也无法利用索引做排序。数据库查询中范围条件非常高频BETWEEN、、都需要有序结构。普通 B 树非叶子节点不仅保存索引键值也可能保存数据或数据指针导致每个节点能容纳的键值数量变少。同样 16KB 的数据页键值密度低树就会更高磁盘 IO 次数更多。B 树的非叶子节点只保存键值可以放入更多键所以树更矮。B 树叶子节点用链表串联范围查询时找到边界后可以顺序遍历效率非常稳定。面试回答时可以按这个顺序陈述磁盘 IO 决定树要高扇出高扇出需要内部节点更紧凑范围查询需要叶子节点有序且连续B 树同时满足这三个条件。5.2 最左前缀到底在说什么最左前缀描述的是复合索引的列顺序规则。复合索引可以理解为先按第一列排序第一列相同时再按第二列排序后面以此类推。因此查询条件必须从第一列开始连续匹配索引才能定位到具体的子树范围。面试中容易踩的坑是把“最左”理解为 SQL 书写顺序。实际上如果 SQL 是WHERE create_time ... AND dept_id 10等值条件下优化器往往能调整顺序改用复合索引。决定因素不是关键词顺序而是索引定义时的列排列。回答时最好给例子索引(a, b, c)能支持a ?、a ? AND b ?、a ? AND b ? AND c ?通常也支持a ? AND c ?但c可能只是作为过滤条件而不是索引定位条件。如果查询只给b ?就跳过了a索引的树形结构无法直接定位。5.3 回表、索引失效和覆盖索引怎么回答回表是二级索引查到主键后再通过聚簇索引取完整行记录的过程。回表次数过多时查询速度会明显下降。优化方向是让查询列全部落在索引内形成覆盖索引。索引失效的常见场景可以归纳为四类场景例子原因对索引列做函数运算DATE(create_time) 2024-03-01B 树存的是原始值无法按函数结果定位隐式类型转换字符串列等于数字转换可能破坏索引列顺序违反最左前缀复合索引只使用第二列树结构无法定位起始边界前导通配符LIKE %keyword无法确定查找起点除此之外优化器也可能因为统计信息过期、单表数据量过少、条件选择性太低主动放弃索引。面试时把“优化器会做综合成本判断”点出来会让回答更完整。5.4 索引设计原则怎么陈述面试里问索引设计不是让你背“多建索引”而是要体现取舍意识。设计索引时先看三类 SQL高频查询的 WHERE 条件、JOIN 的连接列、ORDER BY 和 GROUP BY 的排序列。把高频等值条件放在复合索引最前面范围条件和排序列放在后面。一个复合索引尽量覆盖多个高频查询避免同前缀的重复索引。能加唯一索引的列优先使用唯一索引因为唯一约束可以帮助优化器知道最多返回一条记录访问级别可以提升到const或eq_ref。长字符串列上考虑前缀索引例如INDEX idx_name (name(20))但要注意前缀索引无法完全利用覆盖索引。低选择性的列不要单独建索引更不要在每个列上都建单列索引。回答时可以说一句很落地的话索引不是解决的问题越多越好而是用最少的索引维护成本覆盖足够多的核心查询路径。6. 索引建了不用先按这条链路排查6.1 排查顺序和常见原因很多开发者的困惑是索引明明存在EXPLAIN里possible_keys也有但key却是 NULL或者type变成ALL。遇到这种现象按下面的顺序排查先确认查询条件是否对索引列做了函数、运算或隐式类型转换。再确认复合索引列顺序是否满足最左前缀。然后查看表数据量、数据分布和统计信息。接着检查是否因为 OR、LIKE %...等写法导致优化器无法精准定位。最后考虑优化器的成本判断当表很小、需要返回的数据比例很大时全表扫描可能比随机 IO 回表更便宜。现象可能原因检查方式处理建议possible_keys有索引key为 NULL行数太少或选择性太低EXPLAIN观察 rows不必强行加索引可以用FORCE INDEX做实验对比key是索引但 rows 很大统计信息过期或条件范围太宽ANALYZE TABLE后重跑更新统计信息缩小查询范围字符串列查数字索引失效隐式类型转换SHOW WARNINGS参数类型与列类型对齐OR 中有一边无索引优化器可能全表扫描分别 EXPLAIN 两个条件改写为 UNION ALL 或给缺失列建索引复合索引只查第二列违反最左前缀查看索引定义调整索引列顺序或改写 SQL排查时借助两个命令SHOW INDEX FROM t_employee; ANALYZE TABLE t_employee;SHOW INDEX可以查看索引列顺序、唯一性、基数等信息。ANALYZE TABLE可以更新统计信息。如果EXPLAIN是因为统计过期导致误判这一步通常就能解决。6.2 调优环境与生产环境的差别学习环境里的索引测试结论不能直接照搬到生产环境。两者差异很大主要体现在数据量、并发、维护时间和可观测性上。维度学习环境生产环境数据量几万到几十万行几百万到几亿行数据分布均匀生成存在热点和倾斜统计信息手动 ANALYZE自动统计、定期维护索引变更随时 ALTER低峰期执行工单审批回滚预案验证方式EXPLAIN 估算慢查询日志、监控、只读从库回放在测试环境加一个索引只需要几秒在生产大表上加索引可能带来长事务和主从延迟。生产环境的高频 SQL 调优应该先在测试库用接近真实的数据量压测再通过自动发布流程去灰度执行。不要拿生产表直接做各种ALTER TABLE实验。6.3 关于死锁和写入开销的提醒索引调优不光是读性能问题。每增加一个二级索引INSERT、UPDATE、DELETE 都需要同步维护索引树。对于写密集的表过多索引会让写入放大明显甚至引发锁等待和死锁。死锁常见于多个事务以不同顺序加锁。例如事务 A 先更新部门 10 的行事务 B 先更新部门 20 的行然后两个事务又交叉更新对方的数据若二级索引的插入顺序不一致也会出现锁间隙竞争。排查死锁可以使用SHOW ENGINE INNODB STATUS;只看输出的LATEST DETECTED DEADLOCK部分重点看两个事务的持锁和等待锁顺序再调整业务侧更新顺序或者精简不必要索引。注意索引优化的目标不是让所有 SQL 都用索引而是让核心 SQL 的访问路径稳定、可预期。为了消除一个低频慢查询而新增一个索引可能拖慢大量高频写入这种取舍必须在测试环境里量化。7. 建立可持续的索引优化流程7.1 先开慢查询日志不要靠猜调优慢 SQL第一步不是改代码而是收集证据。MySQL 支持动态开启慢查询日志学习环境可以直接执行SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;这样会把执行时间超过 1 秒的 SQL 记入慢日志。查看当前配置SHOW VARIABLES LIKE slow_query_log%; SHOW VARIABLES LIKE long_query_time;生产环境不要把long_query_time设得过小否则日志量会很大。常见做法是先用 1 到 2 秒作为阈值观察一段时间后再决定是否调低。如果更关心正在执行的 SQL可以查performance_schema或sys库中的会话信息但都需要相应权限。7.2 一条慢 SQL 的标准处理流程拿到慢 SQL 后不要直接加索引。推荐按下面的步骤处理记录 SQL 出现的业务入口、执行频率、影响时间和当前执行计划。使用EXPLAIN查看 type、key、rows、Extra。判断是否符合最左前缀索引列有没有函数或隐式类型转换。查看表统计信息是否过期必要时先ANALYZE TABLE。结合业务条件评估数据分布决定是改写 SQL 还是新增索引。在测试库执行同样的 SQL记录耗时和EXPLAIN前后对比。上线后继续观察慢日志和监控确认索引确实被使用且没有引入写入瓶颈。其中最关键的是第六步。如果一个查询从 3000 毫秒降到 30 毫秒但不能稳定复现说明数据分布或统计信息变化很大仍要继续观察。7.3 发布前索引检查清单下面这份清单可以直接用在代码评审或数据库变更工单里。检查项检查方式预期结果关键 SQL 都有 EXPLAIN 记录收集上线涉及的 SQLtype 至少为 range 或 ref复合索引顺序符合最左前缀核对索引定义与 WHERE 条件没有大量跳过左前列的查询参数类型与字段类型一致检查 ORM 实体和 SQL 绑定参数不存在隐式转换风险SELECT 包含必要字段查看高频查询避免无谓回表和SELECT *索引覆盖核心高频查询对照慢日志核心 SQL 没有全表扫描大表 DDL 安排在低峰期确认变更计划和回滚方案有明确开始和结束时间索引数量不会过度膨胀SHOW INDEX与慢日志对照没有大量重复或冗余索引统计信息有维护机制查看自动统计配置不会长期使用过期统计这八项检查不是让所有表都建满索引而是要求每次索引变更都有依据、可回滚、可验证。对新手来说最有价值的练习是拿到一条慢 SQL先把EXPLAIN输出抄下来再分析每一步最后再动手改。长期坚持判断索引失效和设计索引的自然就会准确很多。索引调优没有银弹真正的能力来自对存储结构的理解和对执行计划的持续观察。掌握 B 树的工作方式、EXPLAIN的字段含义、最左前缀和覆盖索引这些基础之后再反向去看线上慢 SQL就不会再靠运气加索引了。下一步可以把范围扩展到 MySQL 8.0 的直方图、不可见索引、descending index 和优化器提示把这些工具和实际 SQL 结合起来调优能力才会真正稳定下来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价