OceanBaseデータベースは現在、コストベースのクエリリライトとしてOR-Expansionのみをサポートしています。
データベースにおける多くの高度なリライトルール(例:Complex View Mergeやウィンドウ関数のリライト)は、コストベースでのリライトが必要です。OceanBaseデータベースの今後のバージョンでは、これらの複雑なリライトルールがサポートされる予定です。
OR-Expansion
OR-Expansionは、クエリをUNIONで構成された複数のサブクエリにリライトするものです。各サブクエリに対して最適化の余地を広げることができますが、複数のサブクエリの実行を引き起こすため、リライトはコストベースで判断される必要があります。
OR-Expansionのリライトには、主に以下の3つの役割があります:
各ブランチで異なるインデックスを使用してクエリを高速化できるようにする。
以下の例のように、クエリQ1はQ2の形式にリライトされます。ここで、Q2の述語
LNNVL(t1.a = 1)は、2つのサブクエリが重複する結果を生成しないことを保証します。リライトしない場合、Q1は一般的にメインテーブルをアクセスパスとして選択します。Q2については、t1テーブルにインデックス(a)とインデックス(b)が存在する場合、このリライトにより、Q2内の各サブクエリがインデックスをアクセスパスとして選択できる可能性があります。Q1: obclient> SELECT * FROM t1 WHERE t1.a = 1 OR t1.b = 1; Q2: obclient> SELECT * FROM t1 WHERE t1.a = 1 UNION ALL SELECT * FROM t1.b = 1 AND LNNVL(t1.a = 1);完全な例は以下のとおりです:
obclient> CREATE TABLE t1(a INT, b INT, c INT, d INT, e INT, INDEX IDX_a(a), INDEX IDX_b(b)); Query OK, 0 rows affected
/OR-EXPANSIONによるリライトを行わない場合、このクエリはメインテーブルのアクセスパスのみを使用できます/ obclient> EXPLAIN SELECT/+NO_REWRITE()/ * FROM t1 WHERE t1.a = 1 OR t1.b = 1; +--------------------------------------------------------------+ | Query Plan | +--------------------------------------------------------------+ | =================================== |ID|OPERATOR |NAME|EST. ROWS|COST|
Outputs & filters:
0 - output([t1.a], [t1.b], [t1.c], [t1.d], [t1.e]), filter([t1.a = 1 OR t1.b = 1]),
access([t1.a], [t1.b], [t1.c], [t1.d], [t1.e]), partitions(p0)
/書き換え後、各サブクエリは異なるインデックスアクセスパスを使用できる/ obclient>EXPLAIN SELECT * FROM t1 WHERE t1.a = 1 OR t1.b = 1; +------------------------------------------------------------------------+ | Query Plan | +------------------------------------------------------------------------+ | ========================================= |ID|OPERATOR |NAME |EST. ROWS|COST|
Outputs & filters:
0 - output([UNION(t1.a, t1.a)], [UNION(t1.b, t1.b)], [UNION(t1.c, t1.c)], [UNION(t1.d, t1.d)], [UNION(t1.e, t1.e)]), filter(nil)
1 - output([t1.a], [t1.b], [t1.c], [t1.d], [t1.e]), filter(nil),
access([t1.a], [t1.b], [t1.c], [t1.d], [t1.e]), partitions(p0)
2 - output([t1.a], [t1.b], [t1.c], [t1.d], [t1.e]), filter([lnnvl(t1.a = 1)]),
access([t1.a], [t1.b], [t1.c], [t1.d], [t1.e]), partitions(p02
* 各ブランチで異なる結合アルゴリズムを使用してクエリを高速化し、デカルト積結合の使用を回避する。
以下の例のように、クエリQ1はQ2の形式にリライトされます。Q1については、結合方法はNested Loop Join(デカルト積)しかありませんが、リライト後は、各サブクエリがNested Loop Join、Hash Join、またはMerge Joinを選択できるため、より多くの最適化の余地が生まれます。
```javascript
Q1:
obclient> SELECT * FROM t1, t2 WHERE t1.a = t2.a OR t1.b = t2.b;
Q2:
obclient> SELECT * FROM t1, t2 WHERE t1.a = t2.a UNION ALL
SELECT * FROM t1, t2 WHERE t1.b = t2.b AND LNNVL(t1.a = t2.a);
完全な例は以下のとおりです:
obclient> CREATE TABLE t1(a INT, b INT);
Query OK, 0 rows affected
obclient> CREATE TABLE t2(a INT, b INT);
Query OK, 0 rows affected
/*リライトしない場合は、Nested Loop Joinのみを使用できます*/
obclient> EXPLAIN SELECT/*+NO_REWRITE()*/ * FROM t1, t2
WHERE t1.a = t2.a OR t1.b = t2.b;
+--------------------------------------------------------------------------+
| Query Plan |
+--------------------------------------------------------------------------+
| ===========================================
|ID|OPERATOR |NAME|EST. ROWS|COST |
-------------------------------------------
|0 |NESTED-LOOP JOIN| |3957 |585457|
|1 | TABLE SCAN |t1 |1000 |499 |
|2 | TABLE SCAN |t2 |4 |583 |
===========================================
Outputs & filters:
-------------------------------------
0 - output([t1.a], [t1.b], [t2.a], [t2.b]), filter(nil),
conds(nil), nl_params_([t1.a], [t1.b])
1 - output([t1.a], [t1.b]), filter(nil),
access([t1.a], [t1.b]), partitions(p0)
2 - output([t2.a], [t2.b]), filter([? = t2.a OR ? = t2.b]),
access([t2.a], [t2.b]), partitions(p0)
/*リライト後、各サブクエリでHash Joinが使用されている*/
obclient> EXPLAIN SELECT * FROM t1, t2 WHERE t1.a = t2.a OR t1.b = t2.b;
+--------------------------------------------------------------------------+
| Query Plan |
+--------------------------------------------------------------------------+
|ID|OPERATOR |NAME|EST. ROWS|COST|
-------------------------------------
|0 |UNION ALL | |2970 |9105|
|1 | HASH JOIN | |1980 |3997|
|2 | TABLE SCAN|t1 |1000 |499 |
|3 | TABLE SCAN|t2 |1000 |499 |
|4 | HASH JOIN | |990 |3659|
|5 | TABLE SCAN|t1 |1000 |499 |
|6 | TABLE SCAN|t2 |1000 |499 |
=====================================
Outputs & filters:
-------------------------------------
0 - output([UNION(t1.a, t1.a)], [UNION(t1.b, t1.b)], [UNION(t2.a, t2.a)], [UNION(t2.b, t2.b)]), filter(nil)
1 - output([t1.a], [t1.b], [t2.a], [t2.b]), filter(nil),
equal_conds([t1.a = t2.a]), other_conds(nil)
2 - output([t1.a], [t1.b]), filter(nil),
access([t1.a], [t1.b]), partitions(p0)
3 - output([t2.a], [t2.b]), filter(nil),
access([t2.a], [t2.b]), partitions(p0)
4 - output([t1.a], [t1.b], [t2.a], [t2.b]), filter(nil),
equal_conds([t1.b = t2.b]), other_conds([lnnvl(t1.a = t2.a)])
5 - output([t1.a], [t1.b]), filter(nil),
access([t1.a], [t1.b]), partitions(p0)
6 - output([t2.a], [t2.b]), filter(nil),
access([t2.a], [t2.b]), partitions(p0)
各ブランチでソートを個別に除去し、より迅速にTOP-K結果を取得できるようにする。
以下の例のように、クエリQ1はQ2にリライトされます。Q1の実行方法は、条件を満たす行数を特定してからソートし、最終的にTOP-10結果を取得することしかありません。Q2については、インデックス(a,b)が存在する場合、Q2内の2つのサブクエリはインデックスを使用してソートを除去できます。各サブクエリがTOP-10結果を取得した後、最終的にこの20行のデータを1回ソートして、最終的なTOP-10行を取得します。
Q1: obclient> SELECT * FROM t1 WHERE t1.a = 1 OR t1.a = 2 ORDER BY b LIMIT 10; Q2: obclient> SELECT * FROM (SELECT * FROM t1 WHERE t1.a = 1 ORDER BY b LIMIT 10 UNION ALL SELECT * FROM t1 WHERE t1.a = 2 ORDER BY b LIMIT 10) AS TEMP ORDER BY temp.b LIMIT 10;完全な例は以下のとおりです:
obclient> CREATE TABLE t1(a INT, b INT, INDEX IDX_a(a, b)); Query OK, 0 rows affected
/書き換えない場合、最終的にTOP-Kの結果を取得するためにソートが必要です/ obclient> EXPLAIN SELECT/+NO_REWRITE()/ * FROM t1 WHERE t1.a = 1 OR t1.a = 2 ORDER BY b LIMIT 10; +-------------------------------------------------------------------------+ | Query Plan | +-------------------------------------------------------------------------+ | ========================================== |ID|OPERATOR |NAME |EST. ROWS|COST|
Outputs & filters:
0 - output([t1.a], [t1.b]), filter(nil), limit(10), offset(nil)
1 - output([t1.a], [t1.b]), filter(nil), sort_keys([t1.b, ASC]), topn(10)
2 - output([t1.a], [t1.b]), filter(nil),
access([t1.a], [t1.b]), partitions(p0)
/* 替換を行うと、ソート演算子が除去され、最終的にTOP-K結果を取得できる*/ obclient>EXPLAIN SELECT * FROM t1 WHERE t1.a = 1 OR t1.a = 2 ORDER BY b LIMIT 10; +-------------------------------------------------------------------------+ | Query Plan | +-------------------------------------------------------------------------+ | =========================================== |ID|OPERATOR |NAME |EST. ROWS|COST|
Outputs & filters:
0 - output([UNION(t1.a, t1.a)], [UNION(t1.b, t1.b)]), filter(nil), limit(10), offset(nil)
1 - output([UNION(t1.a, t1.a)], [UNION(t1.b, t1.b)]), filter(nil), sort_keys([UNION(t1.b, t1.b), ASC]), topn(10)
2 - output([UNION(t1.a, t1.a)], [UNION(t1.b, t1.b)]), filter(nil)
3 - output([t1.a], [t1.b]), filter(nil),
access([t1.a], [t1.b]), partitions(p0),
limit(10), offset(nil)
4 - output([t1.a], [t1.b]), filter([lnnvl(t1.a = 1)]),
access([t1.a], [t1.b]), partitions(p0),
limit(10), offset(nil)