From 144b7c4e5ee43af80ace920bfb5c36126b73f2be Mon Sep 17 00:00:00 2001 From: Yiding Cui Date: Wed, 20 Jul 2022 18:09:34 +0800 Subject: [PATCH 1/7] planner: add the doc for the SEMI_JOIN_REWRITE hint --- optimizer-hints.md | 35 +++++++++++++++++++++++++++++++++++ subquery-optimization.md | 2 ++ 2 files changed, 37 insertions(+) diff --git a/optimizer-hints.md b/optimizer-hints.md index 86b10b578680..9e658a174aae 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -125,6 +125,41 @@ SELECT /*+ HASH_JOIN(t1, t2) */ * FROM t1,t2 WHERE t1.id = t2.id; > > `HASH_JOIN` 的别名是 `TIDB_HJ`,在 3.0.x 及之前版本仅支持使用该别名;之后的版本同时支持使用这两种名称,推荐使用 `HASH_JOIN`。 +### SEMI_JOIN_REWRITE() + +`SEMI_JOIN_REWRITE()` 提示优化器可以将半连接(Semi Join)进行改写,改写为普通的内连接。目前该 Hint 只作用在 EXISTS 子查询,当子查询的结果集比外结果集显著的少的时候,这个改写可以很好的提升执行效率。 + +{{< copyable "sql" >}} + +```sql +EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT 1 from t1 where t1.a=t.a); +EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 from t1 where t1.a=t.a); +``` +```sql ++-----------------------------+---------+-----------+------------------------+---------------------------------------------------+ +| id | estRows | task | access object | operator info | ++-----------------------------+---------+-----------+------------------------+---------------------------------------------------+ +| MergeJoin_9 | 7992.00 | root | | semi join, left key:test.t.a, right key:test.t1.a | +| ├─IndexReader_25(Build) | 9990.00 | root | | index:IndexFullScan_24 | +| │ └─IndexFullScan_24 | 9990.00 | cop[tikv] | table:t1, index:idx(a) | keep order:true, stats:pseudo | +| └─IndexReader_23(Probe) | 9990.00 | root | | index:IndexFullScan_22 | +| └─IndexFullScan_22 | 9990.00 | cop[tikv] | table:t, index:idx(a) | keep order:true, stats:pseudo | ++-----------------------------+---------+-----------+------------------------+---------------------------------------------------+ ++------------------------------+---------+-----------+------------------------+---------------------------------------------------------------------------------------------------------------+ +| id | estRows | task | access object | operator info | ++------------------------------+---------+-----------+------------------------+---------------------------------------------------------------------------------------------------------------+ +| IndexJoin_16 | 1.25 | root | | inner join, inner:IndexReader_15, outer key:test.t1.a, inner key:test.t.a, equal cond:eq(test.t1.a, test.t.a) | +| ├─StreamAgg_39(Build) | 1.00 | root | | group by:test.t1.a, funcs:firstrow(test.t1.a)->test.t1.a | +| │ └─IndexReader_34 | 1.00 | root | | index:IndexFullScan_33 | +| │ └─IndexFullScan_33 | 1.00 | cop[tikv] | table:t1, index:idx(a) | keep order:true | +| └─IndexReader_15(Probe) | 1.25 | root | | index:Selection_14 | +| └─Selection_14 | 1.25 | cop[tikv] | | not(isnull(test.t.a)) | +| └─IndexRangeScan_13 | 1.25 | cop[tikv] | table:t, index:idx(a) | range: decided by [eq(test.t.a, test.t1.a)], keep order:false, stats:pseudo | ++------------------------------+---------+-----------+------------------------+---------------------------------------------------------------------------------------------------------------+ +``` + +在上述例子中可以看到,在使用了 Hint 之后,TiDB 可以选择由表 `t1` 作为驱动表的 IndexJoin 的执行方式。 + ### HASH_AGG() `HASH_AGG()` 提示优化器对指定查询块中所有聚合函数使用 Hash Aggregation 算法。这个算法多线程并发执行,执行速度较快,但会消耗较多内存。例如: diff --git a/subquery-optimization.md b/subquery-optimization.md index f38b37639d7a..d8d6a8da26be 100644 --- a/subquery-optimization.md +++ b/subquery-optimization.md @@ -85,3 +85,5 @@ explain select * from t1 where exists (select * from t2); | └─TableFullScan_11 | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | +------------------------+----------+-----------+---------------+--------------------------------+ ``` + +除了上述优化器会自动进行的优化之外,也可以通过 [`SEMI_JOIN_REWRITE`](/optimizer-hints.md#semijoinrewrite) 进行进一步的改写。这个改写在 EXISTS 子查询的结果集显著小于外面查询的结果集时会有比较好的效果。 From 124c14d728917fcca9eab8f9e5184499939b99a7 Mon Sep 17 00:00:00 2001 From: Yiding Cui Date: Thu, 21 Jul 2022 19:28:35 +0800 Subject: [PATCH 2/7] fix link --- subquery-optimization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subquery-optimization.md b/subquery-optimization.md index d8d6a8da26be..dd2ac2266fe7 100644 --- a/subquery-optimization.md +++ b/subquery-optimization.md @@ -86,4 +86,4 @@ explain select * from t1 where exists (select * from t2); +------------------------+----------+-----------+---------------+--------------------------------+ ``` -除了上述优化器会自动进行的优化之外,也可以通过 [`SEMI_JOIN_REWRITE`](/optimizer-hints.md#semijoinrewrite) 进行进一步的改写。这个改写在 EXISTS 子查询的结果集显著小于外面查询的结果集时会有比较好的效果。 +除了上述优化器会自动进行的优化之外,也可以通过 [`SEMI_JOIN_REWRITE`](/optimizer-hints.md#semi_join_rewrite) 进行进一步的改写。这个改写在 EXISTS 子查询的结果集显著小于外面查询的结果集时会有比较好的效果。 From 120bb554fa845c6cfc2cd027d5e788c608a266e6 Mon Sep 17 00:00:00 2001 From: Yiding Cui Date: Thu, 21 Jul 2022 20:29:53 +0800 Subject: [PATCH 3/7] address comments --- optimizer-hints.md | 3 ++- subquery-optimization.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/optimizer-hints.md b/optimizer-hints.md index 9e658a174aae..dcf75eeee4c8 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -127,7 +127,7 @@ SELECT /*+ HASH_JOIN(t1, t2) */ * FROM t1,t2 WHERE t1.id = t2.id; ### SEMI_JOIN_REWRITE() -`SEMI_JOIN_REWRITE()` 提示优化器可以将半连接(Semi Join)进行改写,改写为普通的内连接。目前该 Hint 只作用在 EXISTS 子查询,当子查询的结果集比外结果集显著的少的时候,这个改写可以很好的提升执行效率。 +`SEMI_JOIN_REWRITE()` 提示优化器可以将半连接(Semi Join)进行改写,改写为普通的内连接。目前该 Hint 只作用在 EXISTS 子查询。在不进行改写的情况下,SEMI JOIN 在选择 Hash Join 的执行方式时,只能够使用子查询去构建哈希表,因此在子查询比外查询结果集大时,执行速度可能会不及预期。在选择 Index Join 的执行方式时,只能够使用外查询作为驱动表,因此在子查询比外查询结果集小时,执行速度可能会不及预期。在使用了改写之后,便可以扩大选择范围,选择更好的执行方式。 {{< copyable "sql" >}} @@ -135,6 +135,7 @@ SELECT /*+ HASH_JOIN(t1, t2) */ * FROM t1,t2 WHERE t1.id = t2.id; EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT 1 from t1 where t1.a=t.a); EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 from t1 where t1.a=t.a); ``` + ```sql +-----------------------------+---------+-----------+------------------------+---------------------------------------------------+ | id | estRows | task | access object | operator info | diff --git a/subquery-optimization.md b/subquery-optimization.md index dd2ac2266fe7..92a8b8cdd36f 100644 --- a/subquery-optimization.md +++ b/subquery-optimization.md @@ -86,4 +86,4 @@ explain select * from t1 where exists (select * from t2); +------------------------+----------+-----------+---------------+--------------------------------+ ``` -除了上述优化器会自动进行的优化之外,也可以通过 [`SEMI_JOIN_REWRITE`](/optimizer-hints.md#semi_join_rewrite) 进行进一步的改写。这个改写在 EXISTS 子查询的结果集显著小于外面查询的结果集时会有比较好的效果。 +除了上述优化器会自动进行的优化之外,也可以通过 [`SEMI_JOIN_REWRITE`](/optimizer-hints.md#semi_join_rewrite) 进行进一步的改写。在不进行改写的情况下,SEMI JOIN 在选择 Hash Join 的执行方式时,只能够使用子查询去构建哈希表,因此在子查询比外查询结果集大时,执行速度可能会不及预期。在选择 Index Join 的执行方式时,只能够使用外查询作为驱动表,因此在子查询比外查询结果集小时,执行速度可能会不及预期。在使用了改写之后,便可以扩大选择范围,选择更好的执行方式。 From f112ff72f6c478db069450a3b97f6549dd9e4549 Mon Sep 17 00:00:00 2001 From: Yiding Cui Date: Wed, 27 Jul 2022 13:59:47 +0800 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: TomShawn <41534398+TomShawn@users.noreply.github.com> --- optimizer-hints.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/optimizer-hints.md b/optimizer-hints.md index dcf75eeee4c8..eeb13b0566a8 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -127,12 +127,19 @@ SELECT /*+ HASH_JOIN(t1, t2) */ * FROM t1,t2 WHERE t1.id = t2.id; ### SEMI_JOIN_REWRITE() -`SEMI_JOIN_REWRITE()` 提示优化器可以将半连接(Semi Join)进行改写,改写为普通的内连接。目前该 Hint 只作用在 EXISTS 子查询。在不进行改写的情况下,SEMI JOIN 在选择 Hash Join 的执行方式时,只能够使用子查询去构建哈希表,因此在子查询比外查询结果集大时,执行速度可能会不及预期。在选择 Index Join 的执行方式时,只能够使用外查询作为驱动表,因此在子查询比外查询结果集小时,执行速度可能会不及预期。在使用了改写之后,便可以扩大选择范围,选择更好的执行方式。 +`SEMI_JOIN_REWRITE()` 提示优化器将查询语句中的半连接 (Semi Join) 改写为普通的内连接。目前该 Hint 只作用于 `EXISTS` 子查询。 + +如果不使用该 Hint 进行改写,Semi Join 在选择 Hash Join 的执行方式时,只能够使用子查询构建哈希表,因此在子查询比外查询结果集大时,执行速度可能会不及预期。Semi Join 在选择 Index Join 的执行方式时,只能够使用外查询作为驱动表,因此在子查询比外查询结果集小时,执行速度可能会不及预期。 + +在使用了 `SEMI_JOIN_REWRITE()` 进行改写后,优化器便可以扩大选择范围,选择更好的执行方式。 {{< copyable "sql" >}} ```sql +-- 不使用 SEMI_JOIN_REWRITE() 进行改写 EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT 1 from t1 where t1.a=t.a); + +-- 使用 SEMI_JOIN_REWRITE() 进行改写 EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 from t1 where t1.a=t.a); ``` From e1b4e3c0cfa7c9e3ab4b65e5ae3e07403748dd16 Mon Sep 17 00:00:00 2001 From: Yiding Cui Date: Wed, 27 Jul 2022 14:05:08 +0800 Subject: [PATCH 5/7] address comments --- optimizer-hints.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/optimizer-hints.md b/optimizer-hints.md index eeb13b0566a8..458e4caca48f 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -138,9 +138,6 @@ SELECT /*+ HASH_JOIN(t1, t2) */ * FROM t1,t2 WHERE t1.id = t2.id; ```sql -- 不使用 SEMI_JOIN_REWRITE() 进行改写 EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT 1 from t1 where t1.a=t.a); - --- 使用 SEMI_JOIN_REWRITE() 进行改写 -EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 from t1 where t1.a=t.a); ``` ```sql @@ -153,6 +150,16 @@ EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 from t | └─IndexReader_23(Probe) | 9990.00 | root | | index:IndexFullScan_22 | | └─IndexFullScan_22 | 9990.00 | cop[tikv] | table:t, index:idx(a) | keep order:true, stats:pseudo | +-----------------------------+---------+-----------+------------------------+---------------------------------------------------+ +``` + +{{< copyable "sql" >}} + +```sql +-- 使用 SEMI_JOIN_REWRITE() 进行改写 +EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 from t1 where t1.a=t.a); +``` + +```sql +------------------------------+---------+-----------+------------------------+---------------------------------------------------------------------------------------------------------------+ | id | estRows | task | access object | operator info | +------------------------------+---------+-----------+------------------------+---------------------------------------------------------------------------------------------------------------+ From cb11ea302386752e86adbe8566fa616f4259b9b7 Mon Sep 17 00:00:00 2001 From: Yiding Cui Date: Wed, 27 Jul 2022 14:23:11 +0800 Subject: [PATCH 6/7] Update subquery-optimization.md Co-authored-by: TomShawn <41534398+TomShawn@users.noreply.github.com> --- subquery-optimization.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/subquery-optimization.md b/subquery-optimization.md index 92a8b8cdd36f..31709e7b47a8 100644 --- a/subquery-optimization.md +++ b/subquery-optimization.md @@ -86,4 +86,8 @@ explain select * from t1 where exists (select * from t2); +------------------------+----------+-----------+---------------+--------------------------------+ ``` -除了上述优化器会自动进行的优化之外,也可以通过 [`SEMI_JOIN_REWRITE`](/optimizer-hints.md#semi_join_rewrite) 进行进一步的改写。在不进行改写的情况下,SEMI JOIN 在选择 Hash Join 的执行方式时,只能够使用子查询去构建哈希表,因此在子查询比外查询结果集大时,执行速度可能会不及预期。在选择 Index Join 的执行方式时,只能够使用外查询作为驱动表,因此在子查询比外查询结果集小时,执行速度可能会不及预期。在使用了改写之后,便可以扩大选择范围,选择更好的执行方式。 +在上述优化中,优化器会自动优化语句执行。除以上情况外,你也可以在语句中添加 [`SEMI_JOIN_REWRITE`](/optimizer-hints.md#semi_join_rewrite) hint 进一步改写语句。 + +如果不使用 `SEMI_JOIN_REWRITE` 进行改写,Semi Join 在选择 Hash Join 的执行方式时,只能够使用子查询构建哈希表,因此在子查询比外查询结果集大时,执行速度可能会不及预期。Semi Join 在选择 Index Join 的执行方式时,只能够使用外查询作为驱动表,因此在子查询比外查询结果集小时,执行速度可能会不及预期。 + +使用 `SEMI_JOIN_REWRITE` 改写后,优化器便可以扩大选择范围,选择更好的执行方式。 From 703124ddc9615c1a455ca844fdda8c72ff4ec5b5 Mon Sep 17 00:00:00 2001 From: TomShawn <41534398+TomShawn@users.noreply.github.com> Date: Thu, 28 Jul 2022 10:43:17 +0800 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Aolin --- optimizer-hints.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optimizer-hints.md b/optimizer-hints.md index 458e4caca48f..f3e4abd22abd 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -137,7 +137,7 @@ SELECT /*+ HASH_JOIN(t1, t2) */ * FROM t1,t2 WHERE t1.id = t2.id; ```sql -- 不使用 SEMI_JOIN_REWRITE() 进行改写 -EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT 1 from t1 where t1.a=t.a); +EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT 1 FROM t1 WHERE t1.a = t.a); ``` ```sql @@ -156,7 +156,7 @@ EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT 1 from t1 where t1.a=t.a); ```sql -- 使用 SEMI_JOIN_REWRITE() 进行改写 -EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 from t1 where t1.a=t.a); +EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 FROM t1 WHERE t1.a = t.a); ``` ```sql