概要: GaussDB(DWS)を使用する場合、クエリを高速化するために、テーブルにインデックスを作成する必要がある場合があります。インデックスが明確に確立されているが、クエリプランでインデックスが使用されていない状況が発生する場合があります。この記事では、いくつかの一般的なシナリオと最適化方法をリストします。
この記事は、HUAWEI CLOUDコミュニティ「GaussDB(DWS)クエリでのインデックス障害の原因」、作成者:飛べない小さなリスから共有されています。
GaussDB(DWS)を使用する場合、クエリを高速化するために、テーブルにインデックスを付ける必要がある場合があります。インデックスが明確に確立されているが、クエリプランでインデックスが使用されていない状況が発生する場合があります。この記事では、いくつかの一般的なシナリオと最適化方法をリストします。
1.返される結果セットが非常に大きい
例として、行に格納されたテーブルのシーケンススキャンとインデックススキャンを取り上げます。
シーケンススキャン:テーブル内のレコードの配置順序に従って、最初から最後まで順番に検索およびスキャンすることであり、各スキャンですべてのレコードを取得する必要があります。これは、テーブルをスキャンするための最も簡単で基本的な方法でもあり、スキャンのコストは比較的高くなります。
インデックススキャン:特定のクエリについて、最初にインデックスをスキャンし、インデックスから要件を満たすレコードの位置(ポインター)を見つけてから、フェッチするテーブル内の特定のページを見つけます。つまり、最初にインデックスを調べます。 、次にテーブルデータを読み取ります。
したがって、2つのスキャン方法の特性によれば、ほとんどの場合、インデックススキャンはシーケンススキャンよりも高速であることがわかります。ただし、取得した結果セットがすべてのデータの大部分(70%以上)を占める場合、インデックススキャンは、最初にインデックスをスキャンしてからテーブルデータを読み取る必要があるため、直接全表スキャンほど高速ではありません。
2.分析なし
分析はテーブルの統計を更新します。テーブルが分析されていない場合、または最後の分析後にテーブルが大量のデータで追加または削除された場合、統計は不正確になり、テーブルにインデックスが付けられない場合があります。
最適化方法:テーブルを分析して統計を更新します。
3.フィルター条件は、関数または暗黙の型変換を使用するため、索引付けは行われません。
計算、関数、および暗黙の型変換がフィルター条件で使用される場合、インデックスが選択されない場合があります。
例:create table test(a int、b text、c date);インデックスは、それぞれa、b、c列に作成されます。
シナリオ1:コンピューティングの使用
以下の実行結果からわかるように、a = 101、a = 102-1は列aのインデックスを使用できますが、a + 1=102はインデックスを使用しません。
postgres=# explain verbose select * from test where a + 1 = 102;
QUERY PLAN
-------------------------------------------------------------------
Streaming (type: GATHER) (cost=0.19..18.25 rows=6 width=14)
Output: a, b, c
Node/s: All datanodes
-> Seq Scan on public.test (cost=0.00..12.25 rows=6 width=14)
Output: a, b, c
Distribute Key: a
Filter: ((test.a + 1) = 102)
(7 rows)
postgres=#
postgres=# explain verbose select * from test where a = 101;
QUERY PLAN
-------------------------------------------------------------------------------------
Streaming (type: GATHER) (cost=0.06..14.27 rows=1 width=14)
Output: a, b, c
Node/s: datanode1
-> Index Scan using test_a_idx on public.test (cost=0.00..8.27 rows=1 width=14)
Output: a, b, c
Distribute Key: a
Index Cond: (test.a = 101)
(7 rows)
postgres=#
postgres=# explain verbose select * from test where a = 102 - 1;
QUERY PLAN
-------------------------------------------------------------------------------------
Streaming (type: GATHER) (cost=0.06..14.27 rows=1 width=14)
Output: a, b, c
Node/s: datanode1
-> Index Scan using test_a_idx on public.test (cost=0.00..8.27 rows=1 width=14)
Output: a, b, c
Distribute Key: a
Index Cond: (test.a = 101)
(7 rows)
最適化方法:このようなシナリオの最適化方法は、可能な限り式の代わりに定数を使用するか、等号の右側に定数計算を書き込むことです。
シナリオ2:関数を使用する
以下の実行結果からわかるように、インデックス付きの列で関数を使用すると、インデックスを選択できなくなります。
postgres=# explain verbose select * from test where to_char(c, 'yyyyMMdd') = to_char(CURRENT_DATE,'yyyyMMdd');
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------
Streaming (type: GATHER) (cost=0.19..21.00 rows=6 width=14)
Output: a, b, c
Node/s: All datanodes
-> Seq Scan on public.test (cost=0.00..15.00 rows=6 width=14)
Output: a, b, c
Distribute Key: a
Filter: (to_char(test.c, 'yyyyMMdd'::text) = to_char(('2021-03-16'::date)::timestamp with time zone, 'yyyyMMdd'::text))
(7 rows)
postgres=#
postgres=# explain verbose select * from test where c = current_date;
QUERY PLAN
-------------------------------------------------------------------------------------
Streaming (type: GATHER) (cost=0.06..14.27 rows=1 width=14)
Output: a, b, c
Node/s: All datanodes
-> Index Scan using test_c_idx on public.test (cost=0.00..8.27 rows=1 width=14)
Output: a, b, c
Distribute Key: a
Index Cond: (test.c = '2021-03-16'::date)
(7 rows)
最適化方法:インデックス付き列での不要な関数呼び出しを最小限に抑えます。
シナリオ3:暗黙的な型変換
このようなシナリオがよく発生します。たとえば、bのタイプはテキストタイプであり、フィルター条件はb = 2です。プランを生成するときに、テキストタイプは暗黙的にbigintタイプに変換され、実際のフィルター条件はここでb::bigint = 2になり、列bのインデックスを無効にします。
postgres=# explain verbose select * from test where b = 2;
QUERY PLAN
-------------------------------------------------------------------
Streaming (type: GATHER) (cost=0.06..18.25 rows=1 width=14)
Output: a, b, c
Node/s: All datanodes
-> Seq Scan on public.test (cost=0.00..12.25 rows=1 width=14)
Output: a, b, c
Distribute Key: a
Filter: ((test.b)::bigint = 2)
(7 rows)
postgres=#
postgres=#
postgres=# explain verbose select * from test where b = '2';
QUERY PLAN
-------------------------------------------------------------------------------------
Streaming (type: GATHER) (cost=0.06..14.27 rows=1 width=14)
Output: a, b, c
Node/s: All datanodes
-> Index Scan using test_b_idx on public.test (cost=0.00..8.27 rows=1 width=14)
Output: a, b, c
Distribute Key: a
Index Cond: (test.b = '2'::text)
(7 rows)
postgres=#
最適化方法:インデックス条件の定数は、暗黙的な型変換を回避するために、可能な限りインデックス列と同じタイプの定数を使用する必要があります。
4.hashjoinの代わりにnestloop+indexscanを使用します
このタイプのステートメントの特徴は、2つのテーブルが関連付けられている場合、一方のテーブルのwhere条件によってフィルター処理される結果セットの行数が非常に少なく、同時に、最終的に満たされる結果セットの行数が少ないことです。状態も非常に小さいです。現時点では、nestloop + indexscanを使用した場合の影響は、多くの場合、ハッシュ結合が原因です。最適な実行計画は次のとおりです。
5番目のレイヤーのインデックス条件:(t1.b = t2.b)が、結合条件をベーステーブルスキャンにプッシュしたことがわかります。
postgres=# explain verbose select t1.a,t1.b from t1,t2 where t1.b=t2.b and t2.a=4;
id | operation | E-rows | E-distinct | E-memory | E-width | E-costs
----+--------------------------------------------------+--------+------------+----------+---------+---------
1 | -> Streaming (type: GATHER) | 26 | | | 8 | 17.97
2 | -> Nested Loop (3,5) | 26 | | 1MB | 8 | 11.97
3 | -> Streaming(type: BROADCAST) | 2 | | 2MB | 4 | 2.78
4 | -> Seq Scan on public.t2 | 1 | | 1MB | 4 | 2.62
5 | -> Index Scan using t1_b_idx on public.t1 | 26 | | 1MB | 8 | 9.05
(5 rows)
Predicate Information (identified by plan id)
-----------------------------------------------
4 --Seq Scan on public.t2
Filter: (t2.a = 4)
5 --Index Scan using t1_b_idx on public.t1
Index Cond: (t1.b = t2.b)
(4 rows)
Targetlist Information (identified by plan id)
------------------------------------------------
1 --Streaming (type: GATHER)
Output: t1.a, t1.b
Node/s: All datanodes
2 --Nested Loop (3,5)
Output: t1.a, t1.b
3 --Streaming(type: BROADCAST)
Output: t2.b
Spawn on: datanode2
Consumer Nodes: All datanodes
4 --Seq Scan on public.t2
Output: t2.b
Distribute Key: t2.a
5 --Index Scan using t1_b_idx on public.t1
Output: t1.a, t1.b
Distribute Key: t1.a
(15 rows)
====== Query Summary =====
---------------------------------
System available mem: 9262694KB
Query Max mem: 9471590KB
Query estimated mem: 5144KB
(3 rows)
オプティマイザがそのような実行プランを選択しない場合は、次の方法で最適化できます。
enable_index_nestloop=onを設定します。
enable_hashjoin=off;を設定します。
enable_seqscan=off;を設定します。
5.ヒントを使用してインデックスを指定する場合、指定されたインデックスメソッドが正しくありません
GaussDB(DWS)の計画ヒントは、現在、3つの指定されたスキャンメソッド(tablescan、indexscan、およびindexonlyscan)をサポートしています。
tablescan:行に格納されたテーブルのSeqスキャン、列に格納されたテーブルのCStoreスキャンなどの全表スキャン
indexscan:最初にインデックスをスキャンしてから、インデックスに従ってテーブルレコードをフェッチします
indexonlyscan:インデックススキャンをカバーします。必要な戻り結果は、スキャンされたインデックスで完全にカバーできます。インデックススキャンと比較して、インデックスのみのスキャンに含まれるフィールドのセットには、クエリステートメントのフィールドが含まれます。このように、対応するインデックスが抽出されるときに、インデックスに従ってテーブルレコードをフェッチする必要がなくなります。
したがって、indexonlyscanが必要なシナリオでは、ヒントでindexscanが指定されている場合、ヒントは有効になりません。
postgres=# explain verbose select/*+ indexscan(test)*/ b from test where b = '1';
WARNING: unused hint: IndexScan(test)
QUERY PLAN
--------------------------------------------------------------------
Streaming (type: GATHER) (cost=3.12..16.88 rows=100 width=2)
Output: b
Node/s: All datanodes
-> Seq Scan on public.test (cost=0.00..10.88 rows=100 width=2)
Output: b
Distribute Key: a
Filter: (test.b = '1'::text)
(7 rows)
postgres=#
postgres=# explain verbose select/*+ indexonlyscan(test)*/ b from test where b = '1';
QUERY PLAN
--------------------------------------------------------------------------------------------
Streaming (type: GATHER) (cost=3.12..56.51 rows=100 width=2)
Output: b
Node/s: All datanodes
-> Index Only Scan using test_b_idx on public.test (cost=0.00..50.51 rows=100 width=2)
Output: b
Distribute Key: a
Index Cond: (test.b = '1'::text)
(7 rows)
最適化方法:ヒントを使用する場合は、indexscanとindexonlyscanを正しく指定してください。
6.全文検索のジンインデックス
テキスト検索を高速化するために、全文検索を実行するときにGINインデックスを作成できます。
create index idxb on test using gin(to_tsvector('english',b));
インデックスを作成するときは、to_tsvectorの2引数バージョンを使用する必要があります。これは、2引数バージョンもクエリで使用され、パラメータ値がインデックスと同じである場合にのみ使用されます。
postgres=# explain verbose select * from test where to_tsvector(b) @@ to_tsquery('cat') order by 1;
QUERY PLAN
--------------------------------------------------------------------------
Streaming (type: GATHER) (cost=22.23..27.87 rows=12 width=14)
Output: a, b, c
Merge Sort Key: test.a
Node/s: All datanodes
-> Sort (cost=21.86..21.87 rows=12 width=14)
Output: a, b, c
Sort Key: test.a
-> Seq Scan on public.test (cost=0.00..21.78 rows=11 width=14)
Output: a, b, c
Distribute Key: a
Filter: (to_tsvector(test.b) @@ '''cat'''::tsquery)
(11 rows)
postgres=#
postgres=# explain verbose select * from test where to_tsvector('english',b) @@ to_tsquery('cat') order by 1;
QUERY PLAN
---------------------------------------------------------------------------------------------------
Streaming (type: GATHER) (cost=16.09..22.03 rows=2 width=14)
Output: a, b, c
Merge Sort Key: test.a
Node/s: All datanodes
-> Sort (cost=16.03..16.03 rows=2 width=14)
Output: a, b, c
Sort Key: test.a
-> Bitmap Heap Scan on public.test (cost=12.00..16.02 rows=1 width=14)
Output: a, b, c
Distribute Key: a
Recheck Cond: (to_tsvector('english'::regconfig, test.b) @@ '''cat'''::tsquery)
-> Bitmap Index Scan on idxb (cost=0.00..12.00 rows=1 width=0)
Index Cond: (to_tsvector('english'::regconfig, test.b) @@ '''cat'''::tsquery)
(13 rows)
最適化方法:2パラメーターバージョンもクエリで使用され、パラメーター値はインデックスの値と同じであることが保証されています。
[フォロー]をクリックして、HUAWEI CLOUDの新技術について初めて学びましょう〜