本記事では、具体的な例を通じて、OceanBaseデータベースにおけるパーティションテーブルのパラレルクエリ、パーティションテーブル以外のパラレルクエリ、複数テーブルのパラレルクエリの有効化と無効化、およびパラレル実行に関連するシステムビューについて説明します。
パーティションテーブルのパラレルクエリを有効にする
パーティションテーブルに対するクエリでは、クエリ対象のパーティション数が1より大きい場合、システムは自動的にパラレルクエリを有効にし、パラレル度DOP値はシステムデフォルトで1と指定されます。
以下の例のように、パーティションテーブルptableを作成し、ptableに対してフルテーブルデータのスキャン操作を実行し、EXPLAINコマンドで生成された実行計画を確認します。実行計画から、パーティションテーブルのデフォルトのパラレルクエリのdop値が1であることがわかります。OceanBaseクラスタに合計3つのOBServerがあり、テーブルptableの16個のパーティションが3つのOBServerに分散している場合、各OBServerはパーティションデータのスキャン処理を実行するためにワーカースレッド(Worker Thread)を1つ起動します。合計で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です。同様に、PARALLELHintを使用することで並列度を変更することもできます。
以下の例のように、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つのパーティションをスキャンする必要があり、デフォルトではパラレルクエリが実行されます。同様に、PARALLELHintを追加することで並列度を変更し、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を参照してください。