From 7bf68fa960b563e00fdbc16059172a6d31a2238a 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 596616901ebe..0b6ca22fc262 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -124,6 +124,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 0daeaca3b81f..71afd99d4641 100644 --- a/subquery-optimization.md +++ b/subquery-optimization.md @@ -84,3 +84,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 3bbc1500d0b581cc8df45798a10792da812a67c9 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 71afd99d4641..4b7b4759c61a 100644 --- a/subquery-optimization.md +++ b/subquery-optimization.md @@ -85,4 +85,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 71f2c5b2d5cfef28124be9271278b751ed83af38 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 0b6ca22fc262..a4b803bf2ba3 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -126,7 +126,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" >}} @@ -134,6 +134,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 4b7b4759c61a..b1ab4a42e958 100644 --- a/subquery-optimization.md +++ b/subquery-optimization.md @@ -85,4 +85,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 a6c7d1a1673f9eb60d41b20268b74ea171a3f7ae 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 a4b803bf2ba3..c1a3fc6729d0 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -126,12 +126,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 2eb49d820315b1ffa2b05cd64d7e69013cdce753 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 c1a3fc6729d0..5f3b1983ccfd 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -137,9 +137,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 @@ -152,6 +149,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 4507300fe1a1d8f8caebb1d856412a09ee11b85c 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 b1ab4a42e958..a6c71eb2fc60 100644 --- a/subquery-optimization.md +++ b/subquery-optimization.md @@ -85,4 +85,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 88074427c78b49cc2361a7f7508953a42904e789 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 5f3b1983ccfd..7cfb91290dbc 100644 --- a/optimizer-hints.md +++ b/optimizer-hints.md @@ -136,7 +136,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 @@ -155,7 +155,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