已采纳
回答正文
回复
讨论正文
该问题的正文、回答与讨论。
MySQL 8.0.32 / InnoDB,一张 900 万行的工单表 t_ticket,有两个单列索引:
idx_priority(INT,分布极不均匀:P1 占 1.2%、P2 占 8%、P3 占 90.8%)idx_created_at(DATETIME,近 7 天占 3%)业务方的高频查询长这样:
SELECT id, title, priority, created_at, assignee_id
FROM t_ticket
WHERE (priority IN (1, 2) AND status = 'OPEN')
OR created_at >= '2025-01-10 00:00:00'
ORDER BY created_at DESC
LIMIT 50;
注意:status 列没有索引(单列选择性只有 12%,单独建索引性价比不高),所以 WHERE 的两个 OR 分支各自只能命中一个索引。
type=ALL 退化为 index_merge,Extra 列出现 Using sort_union(idx_priority, idx_created_at); Using where; Using filesort。SET optimizer_switch='index_merge_union=off,index_merge_sort_union=off,index_merge_intersection=off' 强制走全表扫描 + filesort:P99 = 3,100ms,平均 1,350ms——只比 sort_union 慢 8%。也就是说:优化器选了 index_merge sort_union,但它的代价几乎等于全表扫描,而 UNION ALL 改写才是正解。 这个 gap 我没法接受"优化器判断失误"就完了,想搞清楚背后的判断逻辑。
EXPLAIN FORMAT=JSON 里 cost_info.sort_union.cost 算出来比 scan.cost 低,但实际执行差距不大——说明代价估算模型在这类"OR 两分支选择性都不高 + 最终要 filesort"的场景里偏乐观)。SET optimizer_trace='enabled=on'):看到优化器在 consider_access_paths_for_reference 阶段确实考虑了 index_merge,但最终在 reorder_index_sort_union 里选了 sort_union 而非 union(因为两个索引的命中行集合有重叠,union 需要额外的去重 sort)。idx_priority_status:覆盖了第一个 OR 分支,但第二个分支还是走 idx_created_at,EXPLAIN 仍然走 index_merge sort_union,P99 只降到 2,200ms,改善有限。我不想要"改成 UNION ALL"这种单点建议(我已经知道),而是想搞清楚一个可复用的判断框架:
(priority, status, created_at) 三列联合?还是分别给两个 OR 分支各建一个覆盖索引?有没有"让优化器算出 scan cost < sort_union cost"的索引设计原则?priority 列分布类似,但场景是单列 WHERE 而非 OR 分支。