已采纳
回答正文
回复
讨论正文
该问题的正文与回答。
我负责一套 SaaS 多租户订单库,MySQL 8.0.36(InnoDB,16C64G,buffer_pool 48G),核心表 t_order 约 1.2 亿行,联合索引 idx_tenant_time (tenant_id, created_at)。业务上 95% 的查询都带 tenant_id,按「大多数查询带它」的假设建了这个索引。
最近 3 个月租户从 200 涨到 800,其中 3 个头部租户合计占全表约 47% 的行。同一 SQL:
SELECT id, amount, status FROM t_order
WHERE tenant_id = ? AND created_at >= ? ORDER BY created_at DESC LIMIT 20;
idx_tenant_time,P99 约 3ms;EXPLAIN 直接切到 type=ALL 全表扫,P99 飙到 2.8s;idx_tenant_time 改成覆盖索引 idx_tenant_time_cover (tenant_id, created_at, id, amount, status),头部租户仍然全表扫——说明不是回表成本的问题;ANALYZE TABLE 后立即查,头部租户依然 ALL;小租户正常;FORCE INDEX (idx_tenant_time) 压住计划,头部租户 P99 从 2.8s 降到 180ms——说明索引本身是可用的,纯粹是优化器 cost 判断偏了;information_schema 上的 histogram(ANALYZE TABLE ... UPDATE HISTOGRAM),计划暂时稳定,但 24h 后批跑写入大量数据后又漂移回 ALL。cardinality(tenant_id) 与总行数的比值)到什么阈值附近,优化器会倾向切全表扫?有没有从 information_schema.INDEX_STATISTICS / mysql.innodb_index_stats 直接算出这个临界值的方法?FORCE INDEX 硬编码 vs 应用层按 tenant 路由不同 SQL vs 分区表按 tenant_id 分,哪种在 800 个租户、3 个头部占 47% 的场景下运维成本最低?站内已有几篇讨论租户分布与索引取舍的文章,但都偏向「列序怎么选」和「覆盖索引值不值」,没覆盖「同一索引对不同租户走不同计划」这个动态漂移问题: