TABLE SCAN 演算子はストレージ層とSQL層のインターフェースであり、オプティマイザーがどのインデックスを使用してデータにアクセスするかを示すために使用されます。
OceanBaseデータベースV4.1.0バージョンでは、通常のインデックスとグローバルインデックスについて、インデックスからテーブルへのロードのロジックは TABLE SCAN 演算子にカプセル化されています。実行計画を表示する際には、is_index_back が演算子がテーブルへのロードを必要とするかどうかを示し、is_global_index が演算子がグローバルインデックスをスキャンするかどうかを示します。 例:TABLE SCAN 演算子を含む実行計画
Q1:
obclient> CREATE TABLE t1(c1 INT PRIMARY KEY, c2 INT, c3 INT, c4 INT);
obclient> CREATE INDEX e1 ON t1(c1,c2);
obclient> EXPLAIN SELECT * FROM t1 WHERE c1 = 1;
+--------------------------------------------------------------------------+
| Query Plan |
+--------------------------------------------------------------------------+
| ========================================= |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ----------------------------------------- |
| |0 |TABLE GET|T1 |1 |3 | |
| ========================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T1.C1], [T1.C2], [T1.C3], [T1.C4]), filter(nil), rowset=16 |
| access([T1.C1], [T1.C2], [T1.C3], [T1.C4]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([T1.C1]), range[1 ; 1], |
| range_cond([T1.C1 = 1]) |
+--------------------------------------------------------------------------+
上記の例では、実行計画表示の outputs & filters において、TABLE SCAN 演算子の出力情報を以下のように詳細に示しています:
| 情報名 | 意味 |
|---|---|
| operator | TABLE SCAN 演算子の operator には3つの形式があります:TABLE SCAN、TABLE GET、TABLE SKIP SCAN。
|
| name | データにアクセスするために使用するインデックスを選択します。選択したインデックスの名前はテーブル名の後に続きます。インデックス名がない場合は、主表スキャンが実行されていることを意味します。ここで注意すべき点は、OceanBaseデータベースでは、主表とインデックスの組織構造が同じであり、主表自体もインデックスであるということです。 |
| output | この演算子の出力列。 |
| filter | この演算子のフィルター述語。例のQ1クエリでは、TABLE GET 演算子に filter が設定されていないため、nil となります。 |
| partitions | スキャンするパーティションを指定します。 |
| is_index_back | この演算子がテーブルへのアクセスを必要とするかどうかを示します。例えば、Q1クエリでは主表が選択されているため、テーブルへのアクセスは不要です。 |
| is_global_index | この演算子がグローバルインデックスをスキャンするかどうかを示します。例えば、Q2クエリでは通常のインデックスが使用されているため、is_global_index は false です。Q3クエリでは、t2 テーブルの i2 インデックスが使用されており、i2 インデックスはグローバルインデックスであるため、is_global_index は true です。 |
| range_key/range/range_cond |
|
Q2:
obclient> CREATE INDEX e1 ON t1(c2,c3);
obclient> EXPLAIN SELECT * FROM t1 WHERE c2 < 1 AND c3 < 1 AND c4 < 1;
+---------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan |
+---------------------------------------------------------------------------------------------------------------------------------------------+
| ================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| -------------------------------------------------- |
| |0 |TABLE RANGE SCAN|T1(E1)|1 |5 | |
| ================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T1.C1], [T1.C2], [T1.C3], [T1.C4]), filter([T1.C3 < cast(1, NUMBER(-1, -85))], [T1.C4 < cast(1, NUMBER(-1, -85))]), rowset=16 |
| access([T1.C1], [T1.C2], [T1.C3], [T1.C4]), partitions(p0) |
| is_index_back=true, is_global_index=false, filter_before_indexback[true,false], |
| range_key([T1.C2], [T1.C3], [T1.C1]), range(MIN,MIN,MIN ; 1,MIN,MIN), |
| range_cond([T1.C2 < cast(1, NUMBER(-1, -85))]) |
+---------------------------------------------------------------------------------------------------------------------------------------------+
12 rows in set
上記の例では、実行計画表示の outputs & filters において、TABLE SCAN 演算子の出力情報を以下のように詳細に示しています:
| 情報名 | 意味 |
|---|---|
| operator | TABLE SCAN 演算子の operator には3つの形式があります:TABLE SCAN、TABLE GET、TABLE SKIP SCAN。
TABLE RANGE SCAN はRANGE条件でテーブルからデータを検索する操作を表します。 |
| name | データにアクセスするために使用するインデックスを選択します。選択したインデックスの名前はテーブル名の後に続きます。インデックスの名前がない場合、主表スキャンが実行されていることを意味します。ここで注意すべき点は、OceanBaseデータベースでは、主表とインデックスの組織構造が同じであり、主表自体もインデックスであるということです。 |
| output | この演算子の出力列。 |
| filter | この演算子のフィルター述語。例えば、Q2のクエリでは、filter([T1.C2 < 1], [T1.C3 < 1], [T1.C4 < 1]) は、テーブルT1の全表スキャン結果に対して3つのフィルター条件を適用したことを示します。それぞれ、C2列の値が1未満、C3列の値が1未満、およびC4列の値が1未満です。クエリ実行時にこれらの条件を満たす行は、最終結果セットに含まれます。 |
| partitions | スキャンするパーティションを指定します。 |
| is_index_back | この演算子がテーブルへのアクセスを必要とするかどうかを示します。 例えば、Q1のクエリでは、主表が選択されているため、テーブルへのアクセスは不要です。Q2のクエリでは、インデックス列は(c2,c3)であり、クエリでc4列を返す必要があるため、テーブルへのアクセスが必要です。 |
| is_global_index | この演算子がグローバルインデックスをスキャンするかどうかを示します。例えば、Q2のクエリでは、使用されるインデックスが通常のインデックスであるため、is_global_index は false です。Q3のクエリでは、t2 テーブルの i2 インデックスが使用されており、i2 インデックスはグローバルインデックスであるため、is_global_index は true です。 |
| filter_before_indexback | 各 filter に対応し、その filter がインデックス上で直接計算できるか、インデックスからテーブルへのアクセス後に計算する必要があるかを示します。 例えば、Q2のクエリでは、filter が c3 < 1 の場合、インデックス上で直接計算できるため、テーブルへのアクセス回数を削減できます。filter が c4 < 1 の場合、c4 列を取得するためにテーブルへのアクセスが必要であり、その後に計算が可能になります。 |
| range_key/range/range_cond |
|
Q3:
obclient> CREATE TABLE t2(c1 INT PRIMARY KEY, c2 INT, c3 INT) PARTITION BY HASH(c1) PARTITIONS 4;
obclient> CREATE INDEX i2 ON t2(c2) GLOBAL;
obclient> EXPLAIN SELECT * FROM t2 WHERE c2 = 1;
+-----------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------+
| ============================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| -------------------------------------------------------------- |
| |0 |DISTRIBUTED TABLE RANGE SCAN|T2(I2)|1 |30 | |
| ============================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T2.C1], [T2.C2], [T2.C3]), filter(nil), rowset=16 |
| access([T2.C1], [T2.C2], [T2.C3]), partitions(p0) |
| is_index_back=true, is_global_index=true, |
| range_key([T2.C2], [T2.C1]), range(1,MIN ; 1,MAX), |
| range_cond([T2.C2 = 1]) |
+-----------------------------------------------------------------+
上記の例では、実行計画表示の outputs & filters において、TABLE SCAN 演算子の出力情報を以下のように詳細に示しています:
| 情報名 | 意味 |
|---|---|
| operator | TABLE SCAN 演算子の operator には3つの形式があります:TABLE SCAN、TABLE GET、TABLE SKIP SCAN。
DISTRIBUTED TABLE RANGE SCAN はクエリ実行時に分散テーブルに対して区間スキャンを行うことを示します。 |
| name | データにアクセスするために使用するインデックスを選択します。選択したインデックスの名前はテーブル名の後に続きます。インデックス名がない場合、主テーブルスキャンが実行されていることを意味します。ここで注意すべき点は、OceanBaseデータベースでは、主テーブルとインデックスの組織構造が同じであり、主テーブル自体もインデックスであるということです。 |
| output | この演算子の出力列。 |
| filter | この演算子のフィルター述語。例えば、Q1クエリの TABLE GET 演算子では filter が設定されていないため、nil となります。 |
| partitions | スキャンするパーティションを指定します。 |
| is_index_back | この演算子がテーブルへのアクセスを必要とするかどうかを示します。例えば、Q1クエリでは主テーブルが選択されているため、テーブルへのアクセスは不要です。Q2クエリでは、インデックス列が (c2,c3) であり、Q2クエリでは c4 列を返す必要があるため、テーブルへのアクセスが必要です。 |
| is_global_index | この演算子がグローバルインデックスをスキャンするかどうかを示します。例えば、Q2クエリでは通常のインデックスが使用されているため、is_global_index は false です。Q3クエリでは、t2 テーブルの i2 インデックスが使用されており、i2 インデックスはグローバルインデックスであるため、is_global_index は true です。 |
| filter_before_indexback | 各 filter に対応し、その filter がインデックス上で直接計算できるか、インデックスからテーブルへのアクセス後に計算する必要があるかを示します。例えば、Q2クエリでは、filter が c3 < 1 の場合、インデックス上で直接計算できるため、テーブルへのアクセス回数を削減できます。filter が c4 < 1 の場合、c4 列を取得するためにテーブルへのアクセスが必要であり、その後に計算が可能になります。 |
| range_key/range/range_cond |
|
適用対象
OceanBaseデータベースのMySQLテナントは、Q4の例にある CONNECT BY LEVEL 構文をサポートしていません。
Q4:
obclient> CREATE TABLE t3(pk INT, c1 INT, c2 INT, PRIMARY KEY(pk));
obclient> INSERT INTO t3 (pk,c1,c2) SELECT LEVEL, MOD(LEVEL,3), LEVEL FROM DUAL CONNECT BY LEVEL <= 10000;
obclient> CREATE INDEX i1 ON t3(c1,c2);
obclient> EXPLAIN SELECT /*+ INDEX_SS(t3 i1) */ * FROM t3 WHERE c2 = 1;
+-----------------------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------------------+
| ================================================= |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------- |
| |0 |TABLE SKIP SCAN|T3(I1)|1 |17 | |
| ================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T3.PK], [T3.C1], [T3.C2]), filter([T3.C2 = 1]), rowset=16 |
| access([T3.PK], [T3.C2], [T3.C1]), partitions(p0) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false], |
| range_key([T3.C1], [T3.C2], [T3.PK]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true |
| prefix_columns_cnt = 1 , skip_scan_range(1,MIN ; 1,MAX) |
+-----------------------------------------------------------------------------------------+
上記の例では、実行計画表示の outputs & filters において、TABLE SCAN 演算子の出力情報を以下のように詳細に示しています:
| 情報名 | 意味 |
|---|---|
| operator | TABLE SCAN 演算子の operator には3つの形式があります:TABLE SCAN、TABLE GET、TABLE SKIP SCAN。
|
| name | データにアクセスするために使用するインデックスを選択します。選択したインデックスの名前はテーブル名の後に続きます。インデックス名がない場合は、主テーブルスキャンが実行されていることを意味します。ここで注意すべき点は、OceanBaseデータベースでは、主テーブルとインデックスの組織構造は同じであり、主テーブル自体もインデックスであるということです。 |
| output | この演算子の出力列。 |
| filter | この演算子のフィルター述語。例のQ1クエリでは、TABLE GET 演算子に filter が設定されていないため、nil となります。 |
| partitions | スキャンするパーティションを指定します。 |
| is_index_back | この演算子がテーブルへのアクセスを必要とするかどうかを示します。例えば、Q1クエリでは主テーブルが選択されているため、テーブルへのアクセスは不要です。Q2クエリでは、インデックス列が (c2,c3) であり、Q2クエリでは c4 列を返す必要があるため、テーブルへのアクセスが必要です。 |
| is_global_index | この演算子がグローバルインデックスをスキャンするかどうかを示します。例えば、Q2クエリでは通常のインデックスが使用されているため、is_global_index は false です。Q3クエリでは、t2 テーブルの i2 インデックスが使用されており、i2 インデックスはグローバルインデックスであるため、is_global_index は true です。 |
| filter_before_indexback | 各 filter に対応し、その filter がインデックス上で直接計算できるか、インデックスからテーブルへのアクセス後に計算する必要があるかを示します。例えば、Q2クエリでは、filter が c3 < 1 の場合、インデックス上で直接計算できるため、テーブルへのアクセス回数を削減できます。filter が c4 < 1 の場合、c4 列を取得するためにテーブルへのアクセスが必要であり、その後に計算が可能になります。 |
| range_key/range/range_cond |
|
| prefix_columns_cnt | TABLE SKIP SCAN のプレフィックス列の数を表します。 |
| skip_scan_range | TABLE SKIP SCAN のサフィックス列から抽出するスキャン範囲を表します。 |
Q4クエリにおいて、TABLE SKIP SCAN は特定の条件を満たす場合に既存のインデックスを利用してスキャンを高速化できます。OceanBaseデータベースは、以下の制限条件を満たす場合に TABLE SKIP SCAN を選択しようとします:
- テーブルに統計情報が収集されていること。
- クエリ条件に結合インデックスのサフィックス列が含まれており、他のインデックスの左端のサフィックスではないこと。
- オプティマイザーがTABLE SKIP SCANと完全なテーブルスキャンのコストを比較した結果、TABLE SKIP SCANの方がコストが低いことを判断した場合。
適用対象
OceanBaseデータベースのMySQLテナントでは、現在文字列を数値型に変換する機能はサポートされていません。
Q5:
obclient> CREATE TABLE t1(c1 NUMBER PRIMARY KEY, c2 DATE);
Query OK, 0 rows affected (0.17 sec)
obclient> CREATE INDEX i1 ON t1(c2);
Query OK, 0 rows affected (0.51 sec)
obclient> EXPLAIN SELECT /*+ index(t1 i1) */ c2 FROM t1 WHERE c2 = TIMESTAMP '2024-01-01 11:22:00';
+-----------------------------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------------------------+
| ================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| -------------------------------------------------- |
| |0 |TABLE RANGE SCAN|T1(I1)|1 |4 | |
| ================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T1.C2]), filter(nil), rowset=16 |
| access([T1.C2]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([T1.C2], [T1.C1]), range(2024-01-01 11:22:00,MIN ; 2024-01-01 11:22:00,MAX), |
| range_cond([cast(T1.C2, TIMESTAMP(19, 9)) = '2024-01-01 11:22:00.000000000']) |
+-----------------------------------------------------------------------------------------------+
上記の例では、実行計画表示の outputs & filters において、TABLE RANGE SCAN 演算子の出力情報を以下のように詳細に示しています:
| 情報名 | 意味 |
|---|---|
| operator | TABLE SCAN 演算子の operator には3つの形式があります:TABLE SCAN、TABLE GET、TABLE SKIP SCAN。
|
| name | データにアクセスするために使用するインデックスを選択します。選択したインデックスの名前はテーブル名の後に続きます。インデックス名がない場合は、主テーブルスキャンが実行されていることを意味します。ここで注意すべき点として、OceanBaseデータベースでは、主テーブルとインデックスの組織構造は同じであり、主テーブル自体もインデックスです。 |
| output | この演算子の出力列。 |
| filter | この演算子のフィルター述語。例のQ5クエリでは、TABLE RANGE SCAN 演算子に filter が設定されていないため、nil となります。 |
| partitions | スキャンするパーティションを指定します。 |
| is_index_back | この演算子がテーブルへのアクセスを必要とするかどうかを示します。例えば、Q1クエリでは主テーブルが選択されているため、テーブルへのアクセスは不要です。Q2クエリでは、インデックス列が (c2,c3,c1) であり、クエリで c4 列を返す必要があるため、テーブルへのアクセスが必要です。 |
| is_global_index | この演算子がグローバルインデックスをスキャンするかどうかを示します。例えば、Q2クエリでは通常のインデックスが使用されているため、is_global_index は false です。Q3クエリでは、t2 テーブルの i2 インデックスが使用されており、i2 インデックスはグローバルインデックスであるため、is_global_index は true です。 |
| range_key/range/range_cond |
|