複数テーブルの結合シナリオにおいて、オプティマイザーの重要なタスクの一つは、各テーブル間の結合順序(Join Order)を決定することです。異なる結合順序は中間結果セットのサイズに影響し、それによって計画全体の実行コストが変わるためです。
実行計画の探索空間と計画実行時のメモリ使用量を削減するため、OceanBaseデータベースのオプティマイザーは結合順序を生成する際、主に左深木の結合形式を考慮します。下図は、左深木、右深木、および多分岐木の計画形状を示しています。

OceanBaseデータベースは結合順序を生成する際、System-Rの動的計画法を採用しており、考慮される要因には各テーブルの可能なアクセスパス、Interesting Order、結合アルゴリズム(Nested Loop Join、Block Based Nested Loop Join、またはSort Merge Joinなど)、および異なるテーブル間の結合選択率などが含まれます。
N個のテーブルの結合が与えられた場合、OceanBaseデータベースが結合順序を生成する方法は以下のとおりです:
各ベーステーブルに対してアクセスパスを生成し、コストが最小のアクセスパスとInteresting Orderを持つすべてのパスを保持します。パスがInteresting Orderを持つ場合、その順序は後続の演算子で使用できます。
すべてのテーブル集合のサイズが
i (1 < i <= N)の計画を生成します。OceanBaseデータベースは通常、左深木のみを考慮します。テーブル集合サイズがiの計画は、その自身の計画と1つのベーステーブルの計画から構成できます。OceanBaseデータベースはこの戦略に従い、すべての結合アルゴリズムやInteresting Orderの継承などの要因を考慮して、すべてのテーブル集合サイズがiの計画を生成します。ここでも、コストが最小の計画とInteresting Orderを持つすべての計画のみが保持されます。
同時に、OceanBaseデータベースはヒントメカニズム /*+LEADING(table_name_list)*/ を提供し、複数テーブルの結合順序を制御できます。
以下の例のように、最初に選択される結合順序は、まず t1 テーブルと t2 テーブルの JOIN 結合を行い、その後 t3 テーブルと JOIN 結合を行うことです。ユーザーがまず t2 テーブルと t3 テーブルの JOIN 結合を行い、その後 t1 テーブルと JOIN 結合を行うことを望む場合は、ヒント /*+LEADING(t2,t3,t1)*/ を使用して制御できます。ユーザーがまず t1 テーブルと t3 テーブルの JOIN 結合を行い、その後 t2 テーブルと JOIN 結合を行うことを望む場合は、ヒント /*+LEADING(t1,t3,t2)*/ を使用して制御できます。
obclient> CREATE TABLE t1(c1 INT, c2 INT, PRIMARY KEY(c1));
Query OK, 0 rows affected
obclient> CREATE TABLE t2(c1 INT, c2 INT, PRIMARY KEY(c1));
Query OK, 0 rows affected
obclient> CREATE TABLE t3(c1 INT, c2 INT, PRIMARY KEY(c1));
Query OK, 0 rows affected
obclient> EXPLAIN SELECT * FROM t1,t2,t3 WHERE t1.c1 = t2.c2 AND t2.c1 = t3.c2;
+-----------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------+
| =======================================
|ID|OPERATOR |NAME|EST. ROWS|COST |
---------------------------------------
|0 |HASH JOIN | |98010 |926122|
|1 | TABLE SCAN |T3 |100000 |61860 |
|2 | HASH JOIN | |99000 |494503|
|3 | TABLE SCAN|T1 |100000 |61860 |
|4 | TABLE SCAN|T2 |100000 |61860 |
=======================================
Outputs & filters:
-------------------------------------
0 - output([T1.C1], [T1.C2], [T2.C1], [T2.C2], [T3.C1], [T3.C2]), filter(nil),
equal_conds([T2.C1 = T3.C2]), other_conds(nil)
1 - output([T3.C2], [T3.C1]), filter(nil),
access([T3.C2], [T3.C1]), partitions(p0)
2 - output([T1.C1], [T1.C2], [T2.C1], [T2.C2]), filter(nil),
equal_conds([T1.C1 = T2.C2]), other_conds(nil)
3 - output([T1.C1], [T1.C2]), filter(nil),
access([T1.C1], [T1.C2]), partitions(p0)
4 - output([T2.C2], [T2.C1]), filter(nil),
access([T2.C2], [T2.C1]), partitions(p0)
obclient> EXPLAIN SELECT /*+LEADING(t2,t3,t1)*/* FROM t1,t2,t3 WHERE t1.c1 = t2.c2
AND t2.c1 = t3.c2;
+-----------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------+
| ========================================
|ID|OPERATOR |NAME|EST. ROWS|COST |
----------------------------------------
|0 |HASH JOIN | |98010 |1096613|
|1 | HASH JOIN | |99000 |494503 |
|2 | TABLE SCAN|T2 |100000 |61860 |
|3 | TABLE SCAN|T3 |100000 |61860 |
|4 | TABLE SCAN |T1 |100000 |61860 |
========================================
Outputs & filters:
-------------------------------------
0 - output([T1.C1], [T1.C2], [T2.C1], [T2.C2], [T3.C1], [T3.C2]), filter(nil),
equal_conds([T1.C1 = T2.C2]), other_conds(nil)
1 - output([T2.C1], [T2.C2], [T3.C1], [T3.C2]), filter(nil),
equal_conds([T2.C1 = T3.C2]), other_conds(nil)
2 - output([T2.C2], [T2.C1]), filter(nil),
access([T2.C2], [T2.C1]), partitions(p0)
3 - output([T3.C2], [T3.C1]), filter(nil),
access([T3.C2], [T3.C1]), partitions(p0)
4 - output([T1.C1], [T1.C2]), filter(nil),
access([T1.C1], [T1.C2]), partitions(p0)
obclient> EXPLAIN SELECT /*+LEADING(t1,t3,t2)*/* FROM t1,t2,t3 WHERE t1.c1 = t2.c2
AND t2.c1 = t3.c2;
+-----------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------+
| =============================================================
|ID|OPERATOR |NAME|EST. ROWS |COST |
-------------------------------------------------------------
|0 |HASH JOIN | |98010 |53098071243|
|1 | NESTED-LOOP JOIN CARTESIAN| |10000000000|7964490204 |
|2 | TABLE SCAN |T1 |100000 |61860 |
|3 | MATERIAL | |100000 |236426 |
|4 | TABLE SCAN |T3 |100000 |61860 |
|5 | TABLE SCAN |T2 |100000 |61860 |
=============================================================
Outputs & filters:
-------------------------------------
0 - output([T1.C1], [T1.C2], [T2.C1], [T2.C2], [T3.C1], [T3.C2]), filter(nil),
equal_conds([T1.C1 = T2.C2], [T2.C1 = T3.C2]), other_conds(nil)
1 - output([T1.C1], [T1.C2], [T3.C1], [T3.C2]), filter(nil),
conds(nil), nl_params_(nil)
2 - output([T1.C1], [T1.C2]), filter(nil),
access([T1.C1], [T1.C2]), partitions(p0)
3 - output([T3.C1], [T3.C2]), filter(nil)
4 - output([T3.C2], [T3.C1]), filter(nil),
access([T3.C2], [T3.C1]), partitions(p0)
5 - output([T2.C2], [T2.C1]), filter(nil),
access([T2.C2], [T2.C1]), partitions(p0)