tenant_id必须作为联合索引最左列,否则索引失效;InnoDB B+Tree从左匹配,缺失最左列将触发全表扫描;后续列顺序须严格按WHERE和ORDER BY条件排列,且需验证key非NULL、rows量级合理、类型一致。

tenant_id 必须是联合索引最左列,否则等于没建
多租户共享表中,tenant_id 不是“可选优化项”,而是索引生效的前提。InnoDB 的 B+Tree 索引从左到右匹配,只要 WHERE 条件里没出现最左列,就无法跳过其他租户的数据页——哪怕表上已有 (status, created_at) 索引,EXPLAIN 也会显示 key: NULL,实际执行是全表扫描。
常见错误现象:
- 同一 SQL 在单租户库毫秒级,在多租户库变秒级甚至超时
-
SHOW INDEX FROM orders显示索引存在,但慢查询日志里反复出现type: ALL和巨大rows - 监控看到大量
Using where; Using filesort,而created_at字段明明建了索引
后续列顺序必须贴合真实 WHERE + ORDER BY 组合
加了 tenant_id 只是起点,后面字段怎么排,得看业务 SQL 怎么写,不是按“重要性”拍脑袋。
典型场景示例:
- 订单列表页:常查
WHERE tenant_id = ? AND status IN (?, ?) ORDER BY created_at DESC→ 建INDEX idx_tenant_status_created (tenant_id, status, created_at)。注意:status是等值或IN,放中间;created_at是范围 + 排序,放最后 - 用户搜索:常查
WHERE tenant_id = ? AND (name LIKE '张%' OR email LIKE 'zhang%')→ 单独tenant_id前缀不够,需搭配前缀索引,如INDEX idx_tenant_name_prefix (tenant_id, name(10)) - 统计报表:常查
GROUP BY tenant_id, date(created_at)→ 避免在索引列上用函数,改写为created_at >= ? AND created_at ,索引设为 <code>(tenant_id, created_at)
三个隐形失效点,让 tenant_id 索引形同虚设
tenant_id 在索引最左 ≠ 查询一定走索引。以下情况会直接绕过:
-
tenant_id字段类型不一致:应用传字符串'123',而列定义是INT→ 触发隐式类型转换,索引失效 - 用了负向条件:
WHERE tenant_id != 123或tenant_id NOT IN (123, 456)→ MySQL 放弃该索引,改走全表扫描 - 联合索引中混入范围条件后还指望右列生效:例如索引是
(tenant_id, status, created_at),但查询写成WHERE tenant_id = 123 AND status > 0 AND created_at > '2025-01-01'→created_at列无法利用索引(范围之后的列失效)
验证 tenant_id 索引是否真起效的实操方法
别只看 SHOW CREATE TABLE 或 EXPLAIN 输出里有没有索引名,要确认它被真正用上:
- 用
EXPLAIN FORMAT=TRADITIONAL查看key字段是否非NULL,且rows数量级与单租户数据量接近(而非全表行数) - 开启
slow_query_log,抓取实际执行的 SQL,检查其WHERE是否严格包含tenant_id = ?(不能是!=、IS NULL或缺失) - 在测试环境模拟租户流量,用
performance_schema.table_io_waits_summary_by_table观察该表的READ_DISK_READS是否显著下降
最常被忽略的是类型一致性——哪怕只是前端传参少了个 parseInt(),或者 MyBatis 的 #{} 写成 ${} 导致字符串拼接,都会让整个复合索引失效。索引设计不是建完就完事,得和每一行 SQL 对齐。


















