索引与执行计划
InnoDB 表本身由聚集 B+ Tree 组织。主键索引叶子保存完整行,二级索引叶子保存二级键与主键值,因此二级索引查非覆盖列通常需要回表。
聚集与二级索引
主键会出现在所有二级索引中。宽字符串、频繁更新或完全随机的主键会放大索引、缓存和写入成本。没有显式主键时,InnoDB 会选择第一个合适的 Unique Not Null 索引,否则创建内部行标识。
索引形式
| 类型 | 用途 | 边界 |
|---|---|---|
| B-tree | 等值、范围、排序和前缀匹配 | 默认索引结构 |
| Unique | 强制业务唯一性 | NULL 与 Collation 影响唯一语义 |
| Fulltext | 文本词项搜索 | 分词、停用词和语言能力需验证 |
| Spatial | 空间对象和谓词 | 使用 R-tree 等空间访问结构,需正确 SRID |
| Prefix | 长字符串前缀 | 节省空间但降低选择性,不能覆盖完整值 |
| Functional | 对表达式结果索引 | 表达式必须满足限制,查询表达式需匹配 |
| Invisible | 优化器默认忽略但继续维护 | 用于测试删除影响,Unique 约束仍生效 |
复合索引
sql
CREATE INDEX idx_order_tenant_status_time
ON orders (tenant_id, status, created_at DESC);该索引适合固定租户、状态后按时间倒序读取。复合索引的使用遵循键顺序:前导列缺失、范围条件位置、排序方向和查询返回比例都会影响可用性。
“把选择性最高列放第一位”不是通用规则。列顺序应同时考虑 Equality、Range、Sort、Group、覆盖需求和高频查询组合。
常见访问策略
| 策略 | 含义 |
|---|---|
| Index Range Scan | 扫描连续索引范围 |
| Ref / Eq Ref | 通过等值键定位一行或少量行 |
| Covering Index | 所需列均在索引中,避免回表 |
| Index Condition Pushdown | 在存储引擎层利用索引列提前过滤 |
| Index Merge | 合并多个索引结果,未必优于专用复合索引 |
| Table Scan | 读取大部分数据时可能比随机索引访问更合理 |
函数、隐式类型转换、Collation 不匹配和前导通配符可能让谓词无法形成有效 Index Range,但不能把所有未走索引情况都称为“索引失效”。优化器也可能基于成本主动选择全表扫描。
EXPLAIN 与统计信息
sql
EXPLAIN FORMAT=TREE
SELECT id, created_at
FROM orders
WHERE tenant_id = 10 AND status = 'PAID'
ORDER BY created_at DESC
LIMIT 20;
EXPLAIN ANALYZE
SELECT id FROM orders WHERE tenant_id = 10;EXPLAIN ANALYZE 会真实执行语句,不应对生产写语句随意使用。阅读时比较估算行数与实际行数、循环次数、执行时间、回表量、排序和临时表。
统计信息不准确时可使用 ANALYZE TABLE 更新。Histogram 可补充无索引列的数据分布,但不会替代索引。Hint 适合诊断和应急,不应掩盖 Schema、统计和 SQL 的根本问题。
索引维护
- 重复索引和前缀包含索引会增加所有写操作成本。
- 删除索引前可先设为 Invisible,覆盖完整业务周期观察。
- Online DDL 不等于零锁或零资源消耗,应确认 Algorithm、Lock 和磁盘余量。
- 大 OFFSET 分页即使使用索引仍需跳过前序记录,持续翻页优先 Keyset Pagination。