本記事では、具体的な例を通じて、OceanBaseデータベースにおけるパーティションテーブルのパラレルクエリ、非パーティションテーブルのパラレルクエリ、複数テーブルのパラレルクエリの有効化と無効化、およびパラレル実行に関連するシステムビューについて説明します。
パーティションテーブルのパラレルクエリの有効化
パーティションテーブルに対するクエリにおいて、クエリの対象パーティション数が1より大きい場合、システムは自動的にパラレルクエリを有効化し、並列度DOP値はシステムデフォルトで1に設定されます。
以下の例のように、パーティションテーブルptableを作成し、ptableの全表データに対するスキャン操作を実行します。EXPLAINコマンドで生成された実行計画を確認します。実行計画から、パーティションテーブルのデフォルトのパラレルクエリのdop値が1であることがわかります。OceanBaseクラスタに合計3台のOBServerがあり、テーブルptableの16個のパーティションが3台のOBServerに分散している場合、各OBServerは1つのワーカースレッド(Worker Thread)を起動してパーティションデータのスキャン処理を実行します。テーブルのスキャン処理を実行するためには、合計3つのワーカースレッドを起動する必要があります。
obclient> CREATE TABLE ptable(c1 INT , c2 INT) PARTITION BY HASH(c1) PARTITIONS 16;
Query OK, 0 rows affected
obclient> EXPLAIN SELECT * FROM ptable;
+---------------------------------------------------------------------------------+
| Query Plan |
+---------------------------------------------------------------------------------+
| ============================================================= |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------------------- |
| |0 |PX COORDINATOR | |1 |62 | |
| |1 |└─EXCHANGE OUT DISTR |:EX10000|1 |62 | |
| |2 | └─PX PARTITION ITERATOR| |1 |61 | |
| |3 | └─TABLE FULL SCAN |ptable |1 |61 | |
| ============================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([INTERNAL_FUNCTION(ptable.c1, ptable.c2)]), filter(nil), rowset=16 |
| 1 - output([INTERNAL_FUNCTION(ptable.c1, ptable.c2)]), filter(nil), rowset=16 |
| dop=1 |
| 2 - output([ptable.c1], [ptable.c2]), filter(nil), rowset=16 |
| force partition granule |
| 3 - output([ptable.c1], [ptable.c2]), filter(nil), rowset=16 |
| access([ptable.c1], [ptable.c2]), partitions(p[0-15]) |
| is_index_back=false, is_global_index=false, |
| range_key([ptable.__pk_increment]), range(MIN ; MAX)always true |
+---------------------------------------------------------------------------------+
19 rows in set
パーティションテーブルに対して、PARALLELヒントを追加してパラレルクエリを開始し、dop値を指定します。EXPLAINコマンドで生成された実行計画を確認します。
obclient> EXPLAIN SELECT /*+ PARALLEL(8) */ * FROM ptable;
Query Plan: ==================================================
|ID|OPERATOR |NAME |EST. ROWS|COST |
--------------------------------------------------
|0 |PX COORDINATOR | |1600000 |737992|
|1 | EXCHANGE OUT DISTR|:EX10000|1600000 |618888|
|2 | PX BLOCK ITERATOR| |1600000 |618888|
|3 | TABLE SCAN |ptable |1600000 |618888|
==================================================
Outputs & filters:
-------------------------------------
0 - output([INTERNAL_FUNCTION(ptable.c1, ptable.c2)]), filter(nil)
1 - output([INTERNAL_FUNCTION(ptable.c1, ptable.c2)]), filter(nil), dop=8
2 - output([ptable.c1], [ptable.c2]), filter(nil)
3 - output([ptable.c1], [ptable.c2]), filter(nil),
access([ptable.c1], [ptable.c2]), partitions(p[0-15])
実行計画から、パラレルクエリのdop値が8であることがわかります。クエリ対象のパーティションが配置されているOBServerの数がdop値以下の場合、ワーカースレッド(合計数はdop値に等しい)は一定の戦略に従って関連するOBServerに割り当てられます。クエリ対象のパーティションが配置されているOBServerの数がdop値を超える場合、各OBServerは少なくとも1つのワーカースレッドを起動し、合計で起動するワーカースレッドの数はdop値を超えます。
例えば、dop値が8の場合、16個のパーティションが4台のOBServerノードに均等に分散している場合、各OBServerは2つのワーカースレッドを起動して対応するパーティションをスキャンします(合計8つのワーカースレッドを起動)。16個のパーティションが16台のOBServerノードに分散している場合(各ノードに1つのパーティション)、各OBServerは1つのワーカースレッドを起動して対応するパーティションをスキャンします(合計16個のワーカースレッドを起動)。
パーティションテーブルに対するクエリにおいて、クエリ対象のパーティション数が1以下の場合、システムはパラレルクエリを起動しません。以下の例のように、ptableに対するクエリにフィルタ条件c1=1を追加します。
obclient> EXPLAIN SELECT * FROM ptable WHERE c1 = 1;
+------------------------------------------------------------------------------------+
| Query Plan |
+------------------------------------------------------------------------------------+
| ======================================================= |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------------- |
| |0 |EXCHANGE IN REMOTE | |1 |5 | |
| |1 |└─EXCHANGE OUT REMOTE| |1 |5 | |
| |2 | └─TABLE FULL SCAN |ptable|1 |4 | |
| ======================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([ptable.c1], [ptable.c2]), filter(nil) |
| 1 - output([ptable.c1], [ptable.c2]), filter(nil) |
| 2 - output([ptable.c1], [ptable.c2]), filter([ptable.c1 = 1]), rowset=16 |
| access([ptable.c1], [ptable.c2]), partitions(p1) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false], |
| range_key([ptable.__pk_increment]), range(MIN ; MAX)always true |
+------------------------------------------------------------------------------------+
15 rows in set
実行計画から、クエリの対象パーティション数が1であり、システムがパラレルクエリを起動していないことがわかります。1つのパーティションに対するクエリでもパラレル実行を希望する場合は、PARALLELヒントを追加してパーティション内のパラレルクエリを実行するしかありません。EXPLAINコマンドで生成された実行計画を確認します。
obclient> EXPLAIN SELECT /*+ PARALLEL(8) */ * FROM ptable WHERE c1 = 1;
+------------------------------------------------------------------------------------+
| Query Plan |
+------------------------------------------------------------------------------------+
| ========================================================= |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| --------------------------------------------------------- |
| |0 |PX COORDINATOR | |1 |1 | |
| |1 |└─EXCHANGE OUT DISTR |:EX10000|1 |1 | |
| |2 | └─PX BLOCK ITERATOR| |1 |1 | |
| |3 | └─TABLE FULL SCAN|ptable |1 |1 | |
| ========================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([INTERNAL_FUNCTION(ptable.c1, ptable.c2)]), filter(nil), rowset=16 |
| 1 - output([INTERNAL_FUNCTION(ptable.c1, ptable.c2)]), filter(nil), rowset=16 |
| dop=8 |
| 2 - output([ptable.c1], [ptable.c2]), filter(nil), rowset=16 |
| 3 - output([ptable.c1], [ptable.c2]), filter([ptable.c1 = 1]), rowset=16 |
| access([ptable.c1], [ptable.c2]), partitions(p1) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false], |
| range_key([ptable.__pk_increment]), range(MIN ; MAX)always true |
+------------------------------------------------------------------------------------+
18 rows in set
説明
クエリ対象のパーティション数が1の場合でも、ヒントを使用してパーティション内のパラレルクエリを実行したい場合は、対応するDOP値が2以上である必要があります。DOP値が空または2未満の場合、パラレルクエリは起動しません。
非パーティションテーブルのパラレルクエリの有効化
非パーティションテーブルは本質的には1つのパーティションしか持たないパーティションテーブルであるため、非パーティションテーブルに対するクエリでは、PARALLELヒントを追加する方法でのみパーティション内のパラレルクエリを開始できます。それ以外の場合は、パラレルクエリは起動しません。
以下の例のように、非パーティションテーブル stable を作成し、stable の全表データに対するスキャン操作を実行します。EXPLAIN コマンドで生成された実行計画を確認します。
obclient> CREATE TABLE stable(c1 INT, c2 INT);
Query OK, 0 rows affected
obclient> EXPLAIN SELECT * FROM stable;
+-----------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------+
| ======================================================= |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------------- |
| |0 |EXCHANGE IN REMOTE | |1 |5 | |
| |1 |└─EXCHANGE OUT REMOTE| |1 |5 | |
| |2 | └─TABLE FULL SCAN |stable|1 |4 | |
| ======================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([stable.c1], [stable.c2]), filter(nil) |
| 1 - output([stable.c1], [stable.c2]), filter(nil) |
| 2 - output([stable.c1], [stable.c2]), filter(nil), rowset=16 |
| access([stable.c1], [stable.c2]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([stable.__pk_increment]), range(MIN ; MAX)always true |
+-----------------------------------------------------------------------+
15 rows in set
実行計画から、非パーティションテーブルでヒントを使用しない場合、パラレルクエリは起動しないことがわかります。
非パーティションテーブルに対して、PARALLEL ヒントを追加してパーティション内の並列クエリを開始し、dop の値(2以上)を指定します。EXPLAIN コマンドを使用して生成された実行計画を確認します。
obclient> EXPLAIN SELECT /*+ PARALLEL(4)*/ * FROM stable;
+---------------------------------------------------------------------------------+
| Query Plan |
+---------------------------------------------------------------------------------+
| ========================================================= |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| --------------------------------------------------------- |
| |0 |PX COORDINATOR | |1 |2 | |
| |1 |└─EXCHANGE OUT DISTR |:EX10000|1 |2 | |
| |2 | └─PX BLOCK ITERATOR| |1 |1 | |
| |3 | └─TABLE FULL SCAN|stable |1 |1 | |
| ========================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([INTERNAL_FUNCTION(stable.c1, stable.c2)]), filter(nil), rowset=16 |
| 1 - output([INTERNAL_FUNCTION(stable.c1, stable.c2)]), filter(nil), rowset=16 |
| dop=4 |
| 2 - output([stable.c1], [stable.c2]), filter(nil), rowset=16 |
| 3 - output([stable.c1], [stable.c2]), filter(nil), rowset=16 |
| access([stable.c1], [stable.c2]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([stable.__pk_increment]), range(MIN ; MAX)always true |
+---------------------------------------------------------------------------------+
18 rows in set
複数テーブルの並列クエリの有効化
クエリでは、複数テーブルの JOIN クエリが最も一般的です。複数テーブルのシナリオでは、クエリのパーティション数がすべて1より大きい場合、各テーブルで並列クエリが採用されます。
以下の例のように、まず2つのパーティションテーブル p1table と p2table を作成します。
obclient> CREATE TABLE p1table(c1 INT ,c2 INT) PARTITION BY HASH(c1) PARTITIONS 2;
Query OK, 0 rows affected
obclient> CREATE TABLE p2table(c1 INT ,c2 INT) PARTITION BY HASH(c1) PARTITIONS 4;
Query OK, 0 rows affected
p1table と p2table の JOIN 結果をクエリします。JOIN 条件は p1table.c1=p2table.c2 です。得られた実行計画は以下のとおりです。
obclient> EXPLAIN SELECT p1table.c1, p2table.c1 AS p2_c1 FROM p1table JOIN p2table ON p1table.c1 = p2table.c2;
+-----------------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------------+
| ===================================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| --------------------------------------------------------------------- |
| |0 |PX COORDINATOR | |1 |25 | |
| |1 |└─EXCHANGE OUT DISTR |:EX10001|1 |25 | |
| |2 | └─HASH JOIN | |1 |25 | |
| |3 | ├─PX PARTITION ITERATOR | |1 |8 | |
| |4 | │ └─TABLE FULL SCAN |p1table |1 |8 | |
| |5 | └─EXCHANGE IN DISTR | |1 |17 | |
| |6 | └─EXCHANGE OUT DISTR (PKEY)|:EX10000|1 |16 | |
| |7 | └─PX PARTITION ITERATOR | |1 |16 | |
| |8 | └─TABLE FULL SCAN |p2table |1 |16 | |
| ===================================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([INTERNAL_FUNCTION(p1table.c1, p2table.c1)]), filter(nil), rowset=16 |
| 1 - output([INTERNAL_FUNCTION(p1table.c1, p2table.c1)]), filter(nil), rowset=16 |
| dop=1 |
| 2 - output([p1table.c1], [p2table.c1]), filter(nil), rowset=16 |
| equal_conds([p1table.c1 = p2table.c2]), other_conds(nil) |
| 3 - output([p1table.c1]), filter(nil), rowset=16 |
| affinitize, force partition granule |
| 4 - output([p1table.c1]), filter(nil), rowset=16 |
| access([p1table.c1]), partitions(p[0-1]) |
| is_index_back=false, is_global_index=false, |
| range_key([p1table.__pk_increment]), range(MIN ; MAX)always true |
| 5 - output([p2table.c2], [p2table.c1]), filter(nil), rowset=16 |
| 6 - output([p2table.c2], [p2table.c1]), filter(nil), rowset=16 |
| (#keys=1, [p2table.c2]), dop=1 |
| 7 - output([p2table.c1], [p2table.c2]), filter(nil), rowset=16 |
| force partition granule |
| 8 - output([p2table.c1], [p2table.c2]), filter(nil), rowset=16 |
| access([p2table.c1], [p2table.c2]), partitions(p[0-3]) |
| is_index_back=false, is_global_index=false, |
| range_key([p2table.__pk_increment]), range(MIN ; MAX)always true |
+-----------------------------------------------------------------------------------+
35 rows in set
デフォルトでは、p1table と p2table(両方のテーブルのクエリ対象パーティション数が1より大きい)に対して並列クエリが採用され、デフォルトの dop 値は1です。同様に、PARALLEL ヒントを使用することで並列度を変更できます。
以下の例のように、JOIN 条件を p1table.c1=p2table.c2 と p2table.c1=1 に変更すると、p2table では単一のパーティションのみが選択されます。実行計画は以下のとおりです:
obclient> EXPLAIN SELECT * FROM p1table p1 JOIN p2table p2 ON p1.c1=p2.c2 AND p2.c1=1;
+---------------------------------------------------------------------------------------+
| Query Plan |
+---------------------------------------------------------------------------------------+
| ===================================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| --------------------------------------------------------------------- |
| |0 |PX COORDINATOR | |1 |15 | |
| |1 |└─EXCHANGE OUT DISTR |:EX10001|1 |15 | |
| |2 | └─HASH JOIN | |1 |13 | |
| |3 | ├─PX PARTITION ITERATOR | |1 |8 | |
| |4 | │ └─TABLE FULL SCAN |p1 |1 |8 | |
| |5 | └─EXCHANGE IN DISTR | |1 |5 | |
| |6 | └─EXCHANGE OUT DISTR (PKEY)|:EX10000|1 |5 | |
| |7 | └─TABLE FULL SCAN |p2 |1 |4 | |
| ===================================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([INTERNAL_FUNCTION(p1.c1, p1.c2, p2.c1, p2.c2)]), filter(nil), rowset=16 |
| 1 - output([INTERNAL_FUNCTION(p1.c1, p1.c2, p2.c1, p2.c2)]), filter(nil), rowset=16 |
| dop=1 |
| 2 - output([p1.c1], [p2.c2], [p1.c2], [p2.c1]), filter(nil), rowset=16 |
| equal_conds([p1.c1 = p2.c2]), other_conds(nil) |
| 3 - output([p1.c1], [p1.c2]), filter(nil), rowset=16 |
| affinitize, force partition granule |
| 4 - output([p1.c1], [p1.c2]), filter(nil), rowset=16 |
| access([p1.c1], [p1.c2]), partitions(p[0-1]) |
| is_index_back=false, is_global_index=false, |
| range_key([p1.__pk_increment]), range(MIN ; MAX)always true |
| 5 - output([p2.c2], [p2.c1]), filter(nil), rowset=16 |
| 6 - output([p2.c2], [p2.c1]), filter(nil), rowset=16 |
| (#keys=1, [p2.c2]), is_single, dop=1 |
| 7 - output([p2.c1], [p2.c2]), filter([p2.c1 = 1]), rowset=16 |
| access([p2.c1], [p2.c2]), partitions(p1) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false], |
| range_key([p2.__pk_increment]), range(MIN ; MAX)always true |
+---------------------------------------------------------------------------------------+
32 rows in set
計画からわかるように、p2table は1つのパーティションのみをスキャンする必要があり、デフォルトでは並列クエリは実行されません。p1table は2つのパーティションをスキャンする必要があり、デフォルトでは並列クエリが実行されます。同様に、PARALLEL ヒントを追加することで並列度を変更し、p2table の1パーティションに対するクエリをパーティション内の並列クエリに変更できます。
並列実行関連のシステムビュー
OceanBaseデータベースは、並列実行の実行状態および統計情報を確認するためのシステムビュー GV$OB_SQL_AUDIT/V$OB_SQL_AUDIT を提供しています。
GV$OB_SQL_AUDIT/V$OB_SQL_AUDIT には多数のフィールドが含まれていますが、並列実行に関連するフィールドは以下のとおりです:qc_id、dfo_id、sqc_id、worker_id。
詳細については、(G)V$OB_SQL_AUDITを参照してください。