元ドキュメント: オプティマイザ Hints
オプティマイザ Hints
概要
TDSQL は MySQL 公式の Hint 標準との互換性を基盤に、分散並列実行の特性に特化した並列 Hint を拡張しています。これらの Hint は主にオプティマイザへの指示として使用され、クエリの並列実行戦略をきめ細かく制御し、複雑なクエリのパフォーマンスを向上させます。
PARALLEL / NO_PARALLEL Hint — 並列度制御
PARALLEL Hint を使用して、Parallel Scan を実行するテーブルとその並列度を指定できます。
PARALLEL Hint は、Global、Query Block、Table の 3 つのレベルで指定できます。
PARALLEL(num) -- Global レベル、Query Block レベル
PARALLEL(@qbname num) -- Query Block レベル
PARALLEL(tablename) -- Table レベル
PARALLEL(tablename num) -- Table レベル
NO_PARALLEL -- Global レベル、Query Block レベル
NO_PARALLEL(@qbname) -- Query Block レベル
NO_PARALLEL(tablename) -- Table レベルNO_PARALLEL は並列実行を無効化します。tablename と qbname の構文については MySQL 公式ドキュメントを参照してください。
各レベルの説明
- Global レベル: メインクエリで指定し、query block name と table name が指定されていない場合。この場合、
numはクエリ全体のデフォルト並列度となり、サブクエリは自身のPARALLELHint で上書きできます。 - Query Block レベル: メインクエリ以外で指定するか、query block name が指定されているが table name が指定されていない場合。この場合、
numはそのサブクエリのデフォルト並列度となり、テーブルレベルで上書きできます。 - Table レベル: Hint でテーブル名が指定された場合。テーブル名のみで
numが指定されていない場合、上位(Query Block レベル、max_parallel_degree変数)から並列度が決定されます。最終的に並列度が取得できない場合(max_parallel_degreeが 0)、並列実行は行われません。
Query Block レベルと Global レベル(Query Block レベルが未指定の場合)は、現在の Query Block が並列計画を使用するかどうかを決定できます。NO_PARALLEL が指定された場合、その Query Block は並列最適化フェーズに入りません。テーブルレベルは現在のテーブルが並列スキャンを行うかどうかのみに影響します。つまり、Query Block と Global レベルで Hint が未指定で、max_parallel_degree が 0 より大きい場合、すべてのテーブルが NO_PARALLEL を使用していても、並列オプティマイザは並列スキャンテーブルを見つけて並列実行します。
使用例
-- Global レベル
EXPLAIN select /*+ PARALLEL(4) */ * from t1 where a > 4;
-- Query Block レベル、最上位クエリでのみ有効
EXPLAIN select /*+ QB_NAME(q1) PARALLEL(@q1 4) */ * from t1 where a > 4;
-- Query Block レベル、サブクエリでのみ有効
explain select * from t3 where a = (select /*+ PARALLEL(2) */ count(a) from t3);
-- Table レベル
EXPLAIN select /*+ PARALLEL(t1 4) */ * from t1 where a > 4;
-- Table レベル
EXPLAIN select /*+ PARALLEL(t3@q2 4) */ * from t3 where a = (select /*+ QB_NAME(q2) */ count(a) from t3);
-- Table レベル
EXPLAIN select /*+ PARALLEL(@q2 t3 4) */ * from t3 where a = (select /*+ QB_NAME(q2) */ count(a) from t3);PQ_DISTRIBUTE Hint — データ分散戦略
オプティマイザに対して、クエリプランにおけるデータ再分散操作の追加方法を指示するために使用します。
-- 基本構文
PQ_DISTRIBUTE(tablespec strategy1 strategy2)
PQ_DISTRIBUTE(tablespec strategy)
-- 特定の操作タイプ向け
PQ_DISTRIBUTE(target strategy1 strategy2)- target: 操作対象。
AGGREGATE、SORT、WINDOWをサポートし、それぞれGROUP BY、ORDER BY、ウィンドウ関数の分散戦略を指定します。WINDOWの後にウィンドウ名を指定できます。 - strategy1、strategy2: 再分散戦略。
NONE、GATHER、HASH、BROADCASTをサポートします。
JOIN 操作の分散戦略
PQ_DISTRIBUTE(t1 HASH, HASH)/PQ_DISTRIBUTE(t1@qb1 HASH, HASH)/PQ_DISTRIBUTE(@qb1 t1 HASH HASH): t1 とその前のテーブルの JOIN 時に、まず HASH 再分散を行い、各ノードで JOIN を実行します。PQ_DISTRIBUTE(t1 BROADCAST, NONE): t1 とその前のテーブルの JOIN 時に、前のテーブル(外部テーブル)を Broadcast し、t1 と JOIN を実行します。PQ_DISTRIBUTE(t1 GATHER): t1 テーブルを先に Gather してから、他のテーブルと JOIN を実行します。
使用例:
-- 両方のテーブルを HASH 再分散後に JOIN
SELECT /*+ PQ_DISTRIBUTE(t1 HASH, HASH) */ *
FROM table1 t1 JOIN table2 t2 ON t1.id = t2.id;
-- 外部テーブルをブロードキャスト、内部テーブルは再分散なし
SELECT /*+ PQ_DISTRIBUTE(t1 BROADCAST, NONE) */ *
FROM small_table t1 JOIN large_table t2 ON t1.id = t2.id;
-- テーブルデータを Leader に集約してから JOIN を実行
SELECT /*+ PQ_DISTRIBUTE(t1 GATHER) */ *
FROM table1 t1 JOIN table2 t2 ON t1.id = t2.id;GROUP BY 集約の分散戦略
並列 GROUP BY 操作は最大 2 フェーズで実行されます。第 1 の戦略は第 1 フェーズの GROUP BY 前のデータ分散操作、第 2 の戦略は第 2 フェーズ前のデータ分散操作を示します。戦略が 1 つの場合は 1 フェーズです。
PQ_DISTRIBUTE(AGGREGATE NONE): 完全プッシュダウンの 1 フェーズPQ_DISTRIBUTE(AGGREGATE NONE, GATHER): Worker + Leader の 2 フェーズPQ_DISTRIBUTE(AGGREGATE NONE HASH): Worker 間の 2 フェーズPQ_DISTRIBUTE(AGGREGATE GATHER): Leader のみでの 1 フェーズ
使用例:
-- 完全プッシュダウンの 1 フェーズ集約
SELECT /*+ PQ_DISTRIBUTE(AGGREGATE NONE) */
department, AVG(salary)
FROM employees
GROUP BY department;
-- Worker + Leader の 2 フェーズ集約
SELECT /*+ PQ_DISTRIBUTE(AGGREGATE NONE, GATHER) */
department, AVG(salary)
FROM employees
GROUP BY department;
-- Worker 間の 2 フェーズ集約
SELECT /*+ PQ_DISTRIBUTE(AGGREGATE NONE HASH) */
department, AVG(salary)
FROM employees
GROUP BY department;
-- Leader のみでの 1 フェーズ集約
SELECT /*+ PQ_DISTRIBUTE(AGGREGATE GATHER) */
department, AVG(salary)
FROM employees
GROUP BY department;ORDER BY ソートの分散戦略
PQ_DISTRIBUTE(SORT NONE, GATHER): Worker + Leader のマージソートPQ_DISTRIBUTE(SORT GATHER): Leader のみでのソート
使用例:
-- Worker での局所ソート + Leader でのマージソート
SELECT /*+ PQ_DISTRIBUTE(SORT NONE, GATHER) */ *
FROM large_table
ORDER BY create_time DESC;
-- Leader のみでのグローバルソート
SELECT /*+ PQ_DISTRIBUTE(SORT GATHER) */ *
FROM large_table
ORDER BY create_time DESC;WINDOW 関数の分散戦略
PQ_DISTRIBUTE(WINDOW GATHER)
ウィンドウ関数の計算をすべて Leader ノードで実行します。すべてのデータを Leader ノードに集約した後、単一ノードでウィンドウ関数の計算を行います。
適用シナリオ:
- データ量が比較的少なく、単一ノードで処理可能
- グローバルソートが必要なウィンドウ関数(例:
RANK() OVER (ORDER BY ...)) - ウィンドウ関数のパーティション数が少なく、効果的な並列化ができない
使用例:
-- Leader ノードでグローバルランキングを計算
SELECT /*+ PQ_DISTRIBUTE(WINDOW GATHER) */
student_id,
score,
RANK() OVER (ORDER BY score DESC) as global_rank
FROM exam_scores;
-- 各学生の成績変化トレンドを計算(データ量が少ない場合)
SELECT /*+ PQ_DISTRIBUTE(WINDOW GATHER) */
student_id,
exam_date,
score,
LAG(score) OVER (PARTITION BY student_id ORDER BY exam_date) as prev_score
FROM student_scores;PQ_DISTRIBUTE(WINDOW HASH)
ウィンドウ関数の PARTITION BY 句のカラムに基づいてハッシュ分散を行い、各 Worker ノードでウィンドウ関数の計算を並列実行します。
適用シナリオ:
- データ量が大きく、並列処理が必要
- ウィンドウ関数に明確なパーティションキーがあり、パーティションデータが均一に分散
- 各パーティション内のデータ量が適度で、並列計算に適している
使用例:
-- 科目別にパーティション化して並列でランキングを計算
SELECT /*+ PQ_DISTRIBUTE(WINDOW HASH) */
subject,
student_id,
score,
RANK() OVER (PARTITION BY subject ORDER BY score DESC) as subject_rank
FROM exam_scores;
-- 部門別にパーティション化して給与の累計合計を計算
SELECT /*+ PQ_DISTRIBUTE(WINDOW HASH) */
department,
employee_id,
salary,
SUM(salary) OVER (PARTITION BY department ORDER BY hire_date) as cumulative_salary
FROM employees;PQ_DISTRIBUTE(WINDOW win1 GATHER)
特定の名前付きウィンドウ(WINDOW 句で定義)に対して GATHER 戦略を適用します。
適用シナリオ:
- クエリに複数のウィンドウ関数が含まれ、特定のウィンドウに異なる戦略を適用する必要がある
- 名前付きウィンドウ関数に特別な処理が必要
- 異なる分散戦略を混合使用して複雑なクエリを最適化
使用例:
-- 特定の名前付きウィンドウに GATHER 戦略を使用
SELECT /*+ PQ_DISTRIBUTE(WINDOW win1 GATHER) */
student_id,
subject,
score,
AVG(score) OVER win1 as avg_score,
RANK() OVER (PARTITION BY subject ORDER BY score DESC) as rank
FROM exam_scores
WINDOW win1 AS (PARTITION BY student_id);
-- 複数の名前付きウィンドウに異なる戦略を適用
SELECT /*+ PQ_DISTRIBUTE(WINDOW win1 GATHER) PQ_DISTRIBUTE(WINDOW win2 HASH) */
department,
employee_id,
salary,
AVG(salary) OVER win1 as dept_avg,
SUM(salary) OVER win2 as running_total
FROM employees
WINDOW win1 AS (PARTITION BY department),
win2 AS (PARTITION BY department ORDER BY hire_date);PQ_DISTRIBUTE(WINDOW win1 HASH)
特定の名前付きウィンドウに対して HASH 分散戦略を適用して並列実行します。
適用シナリオ:
- 名前付きウィンドウ関数のデータ量が大きく、並列最適化が必要
- 異なるウィンドウ関数に異なる並列戦略が必要
- 複雑なクエリの実行プランをきめ細かく制御
使用例:
-- 特定のウィンドウに HASH 分散を使用
SELECT /*+ PQ_DISTRIBUTE(WINDOW win1 HASH) */
product_category,
product_id,
sales_amount,
SUM(sales_amount) OVER win1 as category_total,
RANK() OVER (ORDER BY sales_amount DESC) as global_rank
FROM sales_data
WINDOW win1 AS (PARTITION BY product_category);
-- 混合戦略: 1 つのウィンドウに GATHER、もう 1 つに HASH
SELECT /*+ PQ_DISTRIBUTE(WINDOW win1 GATHER) PQ_DISTRIBUTE(WINDOW win2 HASH) */
customer_id,
order_date,
amount,
AVG(amount) OVER win1 as cust_avg, -- 少量データには GATHER
SUM(amount) OVER win2 as running_sum -- 大量データには HASH
FROM orders
WINDOW win1 AS (PARTITION BY customer_id),
win2 AS (PARTITION BY customer_id ORDER BY order_date);SUBQUERY Hint — サブクエリ並列戦略
並列クエリ向けの MySQL SUBQUERY Hint に、2 つの並列関連戦略が追加されました。これらの戦略は MySQL の既存の戦略と混合して使用できます。
- PQ_PRE_EVALUATION: サブクエリを参照する親クエリの実行前にサブクエリを事前実行するよう指定します。並列 Worker はサブクエリの結果を直接読み取れます。
- PQ_INLINE_EVALUATION: サブクエリの事前実行を強制的に無効化し、MySQL のロジックに基づいて親クエリの必要に応じてオンデマンドで実行します。
使用例:
-- IN サブクエリに MATERIALIZATION 戦略を指定し、並列プランで事前実行戦略を使用しない
EXPLAIN FORMAT=TREE
SELECT * FROM t1
WHERE t1.a IN (
SELECT /*+ SUBQUERY(MATERIALIZATION, PQ_INLINE_EVALUATION) */ a
FROM t2
);
-- Derived Table サブクエリ t に事前実行戦略を指定
EXPLAIN FORMAT=TREE
SELECT /*+ NO_MERGE(t) pq_distribute(t1 none) */ t1.a
FROM t1, (
SELECT /*+ subquery(pq_inline_evaluation) */ a
FROM t2
) t
WHERE t1.a = t.a;