我一直在学习 MySQL 中的优化器提示(我的版本是 8.0.36),并且我已经能够在非常简单的场景中尝试它们并且它有效。然而,一旦我添加嵌套级别,一切都会崩溃。
例如,假设我有 4 张桌子:
activities
:id
、rubric_id
、group_id
rubric_templates
:id
,title
rubric_sessions
:id
、template_id
、name
rubric_session_elements
:id
、rubric_session_id
、value
所有 id 都已建立外键。对于给定的评估规会话有许多评估规会话元素,并且对于给定的评估规模板有许多评估规会话。许多活动可以使用相同的标题,并且一个小组中有许多活动。所以任何地方都没有唯一的键。一切都是多对一。
如果我指示优化器在只有两个表的情况下使用
DUPSWEEDOUT
或 MATERIALIZATION
,它会起作用:
-- Get all rubric sessions for a group
EXPLAIN FORMAT=TREE
SELECT /*+ SEMIJOIN(@subq1 MATERIALIZATION) */ rs.*
FROM rubric_sessions rs
WHERE rs.template_id IN (
SELECT /*+ QB_NAME(subq1) */ a.rubric_template_id
FROM activities a
WHERE group_id = 123
);
/*
-> Nested loop inner join (cost=24.6 rows=48)
-> Filter: (`<subquery2>`.rubric_template_id is not null) (cost=0.00417..0.00417 rows=1)
-> Table scan on <subquery2> (cost=3.46..3.46 rows=1)
-> Materialize with deduplication (cost=0.951..0.951 rows=1)
-> Filter: (a.rubric_template_id is not null) (cost=0.851 rows=1)
-> Index lookup on a using group_id (group_id=123) (cost=0.851 rows=1)
-> Index lookup on rs using template_id (template_id=`<subquery2>`.rubric_template_id), with index condition: (rs.template_id = `<subquery2>`.rubric_template_id) (cost=24.6 rows=48)
*/
-- Replace MATERIALIZATION with DUPSWEEDOUT changes the plan to this:
/*
-> Remove duplicate rs rows using temporary table (weedout) (cost=25.4 rows=48)
-> Nested loop inner join (cost=25.4 rows=48)
-> Filter: (a.rubric_template_id is not null) (cost=0.851 rows=1)
-> Index lookup on a using group_id (group_id=123) (cost=0.851 rows=1)
-> Index lookup on rs using template_id (template_id=a.rubric_template_id), with index condition: (rs.template_id = a.rubric_template_id) (cost=24.5 rows=48)
*/
但是,如果我想更进一步,它只会进行除草。它拒绝实现。
-- Get all rubric session elements for a group
EXPLAIN FORMAT=TREE
SELECT /*+ SEMIJOIN(@subq1 MATERIALIZATION) */ rse.*
FROM rubric_session_elements rse
WHERE rse.rubric_session_id IN (
SELECT rs.id
FROM rubric_sessions rs
WHERE rs.template_id IN (
SELECT /*+ QB_NAME(subq1) */ a.rubric_template_id
FROM activities a
WHERE group_id = 123
)
);
/*
-> Remove duplicate (rs, rse) rows using temporary table (weedout) (cost=527 rows=572)
-> Nested loop inner join (cost=527 rows=572)
-> Nested loop inner join (cost=5.92 rows=48)
-> Filter: (a.rubric_template_id is not null) (cost=0.851 rows=1)
-> Index lookup on a using group_id (group_id=123) (cost=0.851 rows=1)
-> Filter: (rs.template_id = a.rubric_template_id) (cost=5.07 rows=48)
-> Covering index lookup on rs using template_id (template_id=a.rubric_template_id) (cost=5.07 rows=48)
-> Index lookup on rse using rubric_session_elements_rubric_session_id_foreign (rubric_session_id=rs.id) (cost=9.68 rows=11.9)
*/
无论我如何更改它,我都无法说服优化器使用 MATERIALIZATION。我已经在外部和中间选择上放置了半连接提示。我已将 QB_NAME 放在中间和内部选择上。我什至尝试过:
SET SESSION optimizer_switch = 'duplicateweedout=off';
但它仍然显示为“除草”。如果我指定
NO_SEMIJOIN(DUPSWEEDOUT)
那么它似乎会恢复到散列连接,但这不是物化。看起来我并没有违反https://dev.mysql.com/doc/refman/8.4/en/subquery-materialization.html中指定的规则,那么我做错了什么?
即使我们强制执行,MYSQL也会根据成本决定相关策略:MATERIALIZATION(或)DUPSWEEDOUT(根据MySQL手册中的SEMIJOIN Hint)。另一方面,还有另一个提示 SUBQUERY,您可以使用它,因为查询中存在嵌套子查询,它使用物化策略。
下面修改后的代码将使用 MATERILIZATION 策略:
EXPLAIN FORMAT=TREE
选择 /+ 子查询(@subq2 物化) / rse。 来自 rubric_session_elements rse WHERE rse.rubric_session_id IN ( 选择 /+ QB_NAME(subq2) / rs.id 来自 rubric_sessions rs WHERE rs.template_id IN ( SELECT /+ QB_NAME(subq1) */ a.rubric_template_id 来自活动a 其中 a.group_id = 123 ) );
如果您需要更多信息,请告诉我。