第 5 章:先读执行计划,再讨论索引是否有用
一条查询在开发机很快,线上却扫描大量记录。原因可能是数据规模、分布、统计信息、条件写法或访问路径发生变化。建立索引之前,应先确认数据库实际打算怎样读取数据。
5.1 用同一份数据比较计划
随书实验建立 2000 行 people 数据,以 email 作为查询条件。没有 email 索引时,JSON 计划中的访问类型为 ALL;增加唯一 email_idx 后,计划选择该索引与相应的等值访问方式。
EXPLAIN FORMAT=JSON
SELECT id FROM people WHERE email='user1000@example.invalid';这个实验没有报告“快了多少倍”。小数据集、缓存状态和共享 CI 机器不适合据此提供生产性能数字。它证明的是同一查询在该条件下改变了访问路径。
5.2 读懂几项关键字段
access_type 描述访问方式,possible_keys 列出可能的索引,key 是实际选择,rows 一般是估计扫描量,filtered 是条件过滤比例。估计值与实际执行值需要区分,不能把 rows 当作已经发生的物理磁盘读取次数。
传统表格计划中的 rows,在本章 JSON 表节点里对应 rows_examined_per_scan;报告应使用实际格式的字段名。本版证据从 2000 改为 1 的也是计划估计,不是一次物理 I/O 测量。
JSON 格式可能省略不适用字段,并把不同查询结构组织成不同层次。实验递归寻找目标表节点,没有假设所有查询永远位于某个固定路径。复杂连接、子查询与不同版本需要重新核对计划结构。
5.3 索引同时影响读、写和锁
索引可以缩小读取范围,也增加写入维护成本与存储占用。对事务来说,访问路径还会影响遇到哪些索引记录,从而改变等待与竞争。添加索引需要测试关键写入和锁定查询,不能只测一个 SELECT。
例如订单查询经常按 tenant_id 与状态过滤,再按创建时间排序,索引设计应从这些真实访问模式出发。单独为每一列建一个索引,不能保证组合条件与排序都高效。
5.4 EXPLAIN 与实际执行各有位置
EXPLAIN 描述计划。需要实际执行信息时,可以在受控环境评估 EXPLAIN ANALYZE,但要知道它会执行查询,且具体支持形式受语句与版本约束。不要把带副作用的语句当作纯查看操作随意运行。
排查慢请求还要确认连接池等待、锁等待、返回行数、网络传输与数据库执行分别耗时多少。查询计划很好,也可能因为事务长时间持锁而让用户等待。
练习。 计划已经使用索引,但一次请求仍然耗时十秒。请列出至少三种不以“再加一个索引”为直接答案的检查方向,并为每个方向写出需要收集的证据。