セミジョインを使用すると、サブクエリを最適化し、クエリの実行回数を削減して、パフォーマンスを向上させることができます。このトピックでは、セミジョインの基礎とその使用方法について説明します。
前提条件
PolarDB クラスターは、以下のいずれかのリビジョンバージョンを実行している PolarDB for MySQL 8.0 クラスターである必要があります。
-
8.0.1.0.5 以降。
-
8.0.2.2.7 以降。
クラスターのバージョンを確認するには、「エンジンバージョンの照会」をご参照ください。
背景情報
MySQL 5.6.5 では、セミジョイン最適化が導入されました。セミジョインは、外部テーブルの行に対して内部テーブル内で一致するものが見つかった場合、外部テーブルから行を返します。内部テーブルに複数の一致がある場合でも、外部テーブルの行は一度だけ返されます。これは、外部テーブルの該当する行ごとにサブクエリを再実行する可能性がある標準的なサブクエリよりも効率的です。セミジョインは、サブクエリを結合に変換してパフォーマンスを向上させます。これにより、オプティマイザーは内部テーブルと外部テーブルを一緒に処理できるようになり、クエリの実行時間が大幅に短縮されます。

戦略
セミジョインは、以下のいずれかの戦略を使用して実装されます。
-
DuplicateWeedout 戦略
この戦略は、
行 IDに基づいて一意の ID を持つ一時テーブルを作成し、重複を排除します。explain select * from t1 where a in (select a from t11); id select_type table partitions type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t11 NULL ALL NULL NULL NULL 0 0.00 Start temporary 1 SIMPLE t1 NULL ALL NULL NULL NULL 3 33.33 Using where; End temporary; Using join buffer (hash join) Warnings: Note 1003 /* select#1 */ select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` semi join (`test`.`t11`) where (`test`.`t1`.`a` = `test`.`t11`.`a`) -
マテリアライゼーション戦略
この戦略は、サブクエリの
nested tablesを一時テーブルにマテリアライズします。その後、オプティマイザーは、外部テーブルと結合する際に、このマテリアライズされたテーブルを検索またはスキャンして重複を排除します。explain select * from t1 where a in (select a from t11); id select_type table partitions type possible_keys key key_len ref rows filtered Extra 1 SIMPLE <subquery2> NULL ALL NULL NULL NULL NULL 0.00 NULL 1 SIMPLE t1 NULL ALL NULL NULL NULL NULL 3 33.33 Using where; Using join buffer (hash join) 2 MATERIALIZED t11 NULL ALL NULL NULL NULL NULL 0 0.00 NULL Warnings: Note 1003 /* select#1 */ select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` semi join (`test`.`t11`) where (`test`.`t1`.`a` = `<subquery2>`.`a`) -
FirstMatch 戦略
この戦略は、順次スキャンを実行します。最初に一致する行が見つかった後、すぐに外部テーブルの次の行に進み、これにより重複を排除します。
EXPLAINステートメントを実行してクエリプランを表示すると、Extra 列のFirstMatch(t1)は、オプティマイザーが FirstMatch セミジョイン戦略を選択したことを示します。explain select * from t1 where a in (select a from t11); id select_type table partitions type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t1 NULL ALL NULL NULL NULL NULL 3 100.00 NULL 1 SIMPLE t11 NULL ALL NULL NULL NULL NULL 0 0.00 Using where; FirstMatch(t1); Using join buffer (hash join) Warnings: Note 1003 /* select#1 */ select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` semi join (`test`.`t11`) where (`test`.`t11`.`a` = `test`.`t1`.`a`) -
LooseScan 戦略
この戦略は、インデックスに基づいて内部テーブルをグループ化します。その後、各グループを外部テーブルと結合して、一致する結合条件を見つけます。一致が見つかった場合、クエリは外部テーブルから行を返し、スキャンは内部テーブルの次のグループに進みます。この方法で、重複する内部テーブルの行の処理を回避します。
EXPLAINの出力において、テーブル t3 の Extra 列のUsing index; LooseScanは、サブクエリが LooseScan 戦略を使用してセミジョインに最適化されたことを示します。explain select count(a) from t2 where a in ( SELECT a FROM t3); id select_type table partitions type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t3 NULL index a a 5 NULL 30000 3.33 Using where; Using index; LooseScan 1 SIMPLE t2 NULL ref a a 5 test.t3.a 1 100.00 Using index Warnings: Note 1003 /* select#1 */ select count(`test`.`t2`.`a`) AS `count(a)` from `test`.`t2` semi join (`test`.`t3`) where (`test`.`t2`.`a` = `test`.`t3`.`a`)
構文
IN または EXISTS サブクエリは、通常、セミジョインをトリガーします。
-
IN
SELECT * FROM Employee WHERE DeptName IN ( SELECT DeptName FROM Dept ) -
EXISTS
SELECT * FROM Employee WHERE EXISTS ( SELECT 1 FROM Dept WHERE Employee.DeptName = Dept.DeptName )
並列セミジョインのパフォーマンス
セミジョイン戦略を使用するクエリに対して、PolarDB はすべてのセミジョイン戦略に並列高速化を提供します。セミジョインタスクを、マルチスレッドモデルを使用して同時に実行される一連のサブタスクに分割します。これにより、重複排除機能が強化され、クエリのパフォーマンスが大幅に向上します。PolarDB 8.0.2.2.7 以降では、マテリアライゼーション戦略に対してマルチフェーズ並列クエリがサポートされており、セミジョインのパフォーマンスがさらに向上しています。以下の Q20 の例でこれを示します。
SELECT
s_name,
s_address
FROM
supplier,
nation
WHERE
s_suppkey IN
(
SELECT
ps_suppkey
FROM
partsupp
WHERE
ps_partkey IN
(
SELECT
p_partkey
FROM
part
WHERE
p_name LIKE '[COLOR]%'
)
AND ps_availqty > (
SELECT
0.5 * SUM(l_quantity)
FROM
lineitem
WHERE
l_partkey = ps_partkey
AND l_suppkey = ps_suppkey
AND l_shipdate >= date('[DATE]')
AND l_shipdate < date('[DATE]') + interval '1' year )
)
AND s_nationkey = n_nationkey
AND n_name = '[NATION]'
ORDER BY
s_name;
この例では、サブクエリと外部クエリの両方が、並列度 (DOP) 32 で並列実行されます。サブクエリは最初に並列でマテリアライズされたテーブルを生成します。その後、外部クエリも並列で実行されます。このアプローチでは、CPU 処理能力を最大限に活用して、クエリの並列処理を最大化します。以下に、100 GB スケールの TPC-H データセットを使用したホットデータシナリオにおけるマルチフェーズ並列処理機能を示します。
この例では、TPC-Hベンチマークに基づくテストが実装されていますが、TPC-Hベンチマークテストのすべての要件を満たしているわけではありません。 そのため、テスト結果は TPC-H のベンチマークテストの公開結果と一致しない可能性があります。
並列クエリプランは以下の通りです。
-> Sort: <temporary>.s_name (cost=5014616.15 rows=100942)
-> Stream results
-> Nested loop inner join (cost=127689.96 rows=100942)
-> Gather (slice: 2; workers: 64; nodes: 2) (cost=6187.68 rows=100928)
-> Nested loop inner join (cost=1052.43 rows=1577)
-> Filter: (nation.N_NAME = 'KENYA') (cost=2.29 rows=3)
-> Table scan on nation (cost=2.29 rows=25)
-> Parallel index lookup on supplier using SUPPLIER_FK1 (S_NATIONKEY=nation.N_NATIONKEY), with index condition: (supplier.S_SUPPKEY is not null), with parallel partitions: 863 (cost=381.79 rows=631)
-> Single-row index lookup on <subquery2> using <auto_distinct_key> (ps_suppkey=supplier.S_SUPPKEY)
-> Materialize with deduplication
-> Gather (slice: 1; workers: 64; nodes: 2) (cost=487376.70 rows=8142336)
-> Nested loop inner join (cost=73888.70 rows=127224)
-> Filter: (part.P_NAME like 'lime%') (cost=31271.54 rows=33159)
-> Parallel table scan on part, with parallel partitions: 6244 (cost=31271.54 rows=298459)
-> Filter: (partsupp.PS_AVAILQTY > (select #4)) (cost=0.94 rows=4)
-> Index lookup on partsupp using PRIMARY (PS_PARTKEY=part.P_PARTKEY) (cost=0.94 rows=4)
-> Select #4 (subquery in condition; dependent)
-> Aggregate: sum(lineitem.L_QUANTITY)
-> Filter: ((lineitem.L_SHIPDATE >= DATE'1994-01-01') and (lineitem.L_SHIPDATE < <cache>((DATE'1994-01-01' + interval '1' year)))) (cost=4.05 rows=1)
-> Index lookup on lineitem using LINEITEM_FK2 (L_PARTKEY=partsupp.PS_PARTKEY, L_SUPPKEY=partsupp.PS_SUPPKEY) (cost=4.05 rows=7)
100 GB スケールの標準的な TPC-H ホットデータシナリオにおける直列実行時間は以下の通りです。
| Supplier#000999085 | egFwcBv5TkH |
| Supplier#000999105 | 1CKYsKKIxqM |
| Supplier#000999253 | q0nlouqFchhsbmkPq |
| Supplier#000999314 | 1MLMPBBnYSnMl1lRRjXiu2B2sxahjItRt0v |
| Supplier#000999319 | hkc5LIrtAz9clk2Edz8ENngn4PdhcSD02YRxN |
| Supplier#000999347 | L,CPr2clOoPg91gYxqCsie7DNf |
| Supplier#000999357 | tQW7OYPfNDzfqqzHQCx |
| Supplier#000999362 | X7 Rxrst808LeHI1sYlVIW5Usqu |
| Supplier#000999394 | TZn2ZOsZCxMmW09 |
| Supplier#000999486 | SMiqFfRyUuXldJp |
| Supplier#000999684 | SwmVOJNeJwTdDJcE0 |
| Supplier#000999814 | Tlh9Z1u5EPk1drhEbiTZpRHJJwTX3FwJoE |
| Supplier#000999841 | 9e5iYCk2pntVLLKnP5YJ3xT2IY0I7gENyfqy |
| Supplier#000999850 | XEzRaermdYPO5XX |
| Supplier#000999902 | D4XvfAYuocmiUFM1N,EScgAHQcF |
| Supplier#000999936 | GkUI05zvDkNpMPlE,AplBgF8PxfEhe |
| Supplier#000999949 | bRcyGJoAryorYRUKGtYfNt4ZlgvC6vZ |
| Supplier#000999956 | 5r fovH1Bwu087yF5L7YHitAZWtmK |
| Supplier#000999969 | 0xHYbgscQREncmbZziaM3dxg51jA,PKhyrAQ |
+--------------------+----------------------------------------------+
17978 rows in set (43.52 sec)
マルチノード並列実行を有効にした場合の実行時間は以下の通りです。
| Supplier#000999085 | egFwcBv5TkH |
| Supplier#000999105 | 1CKYsKKIxqM |
| Supplier#000999253 | q0nlouqFchhsbmkPq |
| Supplier#000999314 | 1MLMPBBnYSnMl1lRRjXiu2B2sxahjItRt0v |
| Supplier#000999319 | hkc5LIrtAz9clk2Edz8ENngn4PdhcSD02YRxN |
| Supplier#000999347 | L,CPr2cl0oPg91gYxqCsie7DNf |
| Supplier#000999357 | tQW7OYPfNDzfqqzHQCx |
| Supplier#000999362 | X7 Rxrst808LeHI1sYlVIW5Usqu |
| Supplier#000999394 | TZn2ZOsZCxMmW09 |
| Supplier#000999486 | SMiqFfRyUuXldJp |
| Supplier#000999684 | SwmVOJNeJwTdDJcE0 |
| Supplier#000999814 | Tlh9Z1u5EPk1drhEbiTZpRHJJwTX3FwJoE |
| Supplier#000999841 | 9e5iYCk2pntVLLKnP5YJ3xT2IY0I7gENyfqy |
| Supplier#000999850 | XEzRaermdYPO5XX |
| Supplier#000999902 | D4XvfAYuocmiUFM1N,EScgAHQcF |
| Supplier#000999936 | GkUI0SzvDkNpMPlE,AplBgF8PxfEhe |
| Supplier#000999949 | bRcyGJoAryorYRUKGtYfNt4ZlgvC6vZ |
| Supplier#000999956 | 5r fovH1Bwu087yF5L7YHitAZWtmK |
| Supplier#000999969 | 0xHYbgscQREncmbZziaM3dxg51jA,PKhyrAQ |
17978 rows in set (2.29 sec)
実行時間は 43.52 秒から 2.29 秒に短縮され、19 倍のパフォーマンス向上を実現しました。