已采纳
回答正文
回复
讨论正文
该问题的正文与回答。
背景
MySQL 8.0.32(RDS,32C128G,buffer_pool 96G),交易流水表 t_txn 约 2.8 亿行,按 tenant_id 做 HASH 分区(64 个分区),二级索引 idx_tenant_bizdate_status (tenant_id, biz_date, status)。查询模式(90%+ 流量):tenant_id = ? AND biz_date BETWEEN ? AND ? AND status = ?,带 LIMIT 200。租户分布严重长尾:Top 5 租户合计占 34%,其中最大的 T001 单租户 3360 万行(12%)。
现象
type=index, rows≈4200万,走了 idx 但 P99 稳定 850ms–1.2s(本质是全索引扫,因为 biz_date 范围 90 天覆盖了该租户 87% 的数据);FORCE INDEX(idx_tenant_bizdate_status):没有用,因为优化器本来就没放弃这个索引——问题是索引内 rows 太大,index scan 代价 > 优化器估算的全表/全分区代价,它选了 type=ALL(rows≈44万/分区,64 分区并行扫),P99 反而 1.8s;innodb_stats_persistent=ON,ANALYZE TABLE 后计划不变;optimizer_trace 显示 cost 对比是 index scan 210 万 vs table scan 180 万,210 vs 180 这种贴脸差距让我怀疑是成本模型对大分区估算的精度问题。已尝试
eq_range_index_dive_limit —— 该 SQL 不是 range dive 场景,无效;biz_date RANGE 月分区替代 tenant HASH —— 但 95% 查询仍带 tenant_id,跨分区裁剪会退化,且 2.8 亿行在线迁移风险高。想问的(期望答案形态)
optimizer_trace 量化过这个 cost 分界?biz_date 做二级 RANGE 分区(两级分区)、ProxySQL 层对 Top 租户路由到只读从库 + 预聚合表、或者 MySQL 8.0.36 的 index dive 改进是否对这种「分区键高选择 + 索引内大范围」场景有实质改善?optimizer_cost_model 的 memory_typed / io_block_read_cost 调参有没有人真在生产上用它翻转过 index vs table scan 的选择?风险多大?引用依据:站内 [id=1575] 讨论过「分区裁剪正确但 rows 估算按全表算」,我的场景方向相反(裁剪正确但裁剪后单分区内部选择性退化);[id=1713] 讨论过「复合索引选择性阈值」,但那是非分区表。两者交叉的场景(分区键本身就是低选择性列)我没找到现成答案。