已采纳
回答正文
回复
讨论正文
该问题的正文、回答与讨论。
SaaS 工单系统,主库 MySQL 8.0.32(InnoDB,32C128G,innodb_buffer_pool_size=96G,NVMe 本地盘),核心表 t_ticket:
id(bigint 自增 PK)、tenant_id(bigint)、status(tinyint,3 个枚举:1=待处理 / 2=处理中 / 3=已解决)、title(varchar(256))、created_at(datetime)、updated_atidx_status_tenant_time (status, tenant_id, created_at DESC)SELECT id, title, created_at
FROM t_ticket
WHERE status = 1 AND tenant_id = 8821
ORDER BY created_at DESC
LIMIT 50;
status=1 下该 tenant 命中 < 5000 行):走 idx_status_tenant_time,P99 约 8ms,正常;status=1 下命中 230 万行,头部 3 个租户占 status=1 总量的 41%):优化器估算 rows≈2300000,偏 全表扫描 + filesort,P99 12.4s,业务方天天报障;ANALYZE TABLE t_ticket 后重跑,估算还是 230 万,决策不变;FORCE INDEX(idx_status_tenant_time) 对比:大租户 P99 6.8s(比全表扫还慢——因为索引段内 status=1 有 2960 万行,按 tenant_id 定位到 8821 的段仍要扫过前面 ~2700 万个非本租户条目,逐行回表取 title);FORCE INDEX(PRIMARY) 全表扫基线:P99 2.4s(buffer pool 命中率 82%,顺序读占优)。ANALYZE TABLE 刷新统计信息 + 重跑 → 优化器决策不变,仍然全表扫;FORCE INDEX(idx_status_tenant_time) vs FORCE INDEX(PRIMARY) 对比 → 大租户走索引 6.8s > 全表扫 2.4s,索引路径反而更差;title 加进索引做覆盖索引 → 索引从 1.8GB 涨到 4.1GB,status=1 段膨胀后大租户走索引 P99 只降到 6.1s,没质变,已回滚;我现在的纠结是:
idx_status_tenant_time 删掉,改成 idx_open (status, tenant_id, created_at) 只保留 status=1 的段(用生成列或分区过滤),或干脆按 status 拆 3 张表/3 个索引。8800 万行 + 4000 TPS 下多维护一棵 B+ 树的写放大到底多少?status 只有 3 个值,拆出来的索引选择性会不会还是差?tenant_id 或 status 做 RANGE / LIST 分区,大租户单独落区。改动面大,业务层要不要改连接路由?期望得到的答案形态:
optimizer_switch 里某个 flag、或 8.0 的索引统计采样精度调整),请给出具体命令和适用边界;我 8 年 DBA 经验,写放大和 MDL 风险我能兜底,但「拆不拆、拆了值不值」这笔账我算不圆,想听社区里真在长尾多租户场景下做过取舍的同行的数据。