すべてのプロダクト
Search
ドキュメントセンター

PolarDB:クエリエグゼキュータ

最終更新日:Jun 23, 2026

このトピックでは、PolarDB-X 1.0 の SQL エグゼキュータが、ストレージレイヤーにプッシュダウンできない SQL クエリの一部を実行する方法について説明します。

基本概念

SQL エグゼキュータは、PolarDB-X 1.0 において論理レイヤーで演算子を実行するコンポーネントです。単純なポイントクエリの場合、クエリ全体が MySQL ストレージレイヤーにプッシュダウンされることがよくあります。このような場合、エグゼキュータの作業は最小限に抑えられ、MySQL の結果が直接クライアントに返されます。しかし、複雑な SQL クエリの場合、一部の演算子はプッシュダウンできません。その場合、PolarDB-X 1.0 のクエリエグゼキュータが残りの計算を実行する必要があります。

例えば、次のクエリを考えてみます。

SELECT l_orderkey, sum(l_extendedprice *(1 - l_discount)) AS revenue
FROM CUSTOMER, ORDERS, LINEITEM
WHERE c_mktsegment = 'AUTOMOBILE'
  and c_custkey = o_custkey
  and l_orderkey = o_orderkey
  and o_orderdate < '1995-03-13'
  and l_shipdate > '1995-03-13'
GROUP BY l_orderkey;

EXPLAIN コマンドを使用して、以下のように PolarDB-X 1.0 の実行計画を表示できます。

HashAgg(group="l_orderkey", revenue="SUM(*)")
  HashJoin(condition="o_custkey = c_custkey", type="inner")
    Gather(concurrent=true)
      LogicalView(tables="ORDERS_[0-7],LINEITEM_[0-7]", shardCount=8, sql="SELECT `ORDERS`.`o_custkey`, `LINEITEM`.`l_orderkey`, (`LINEITEM`.`l_extendedprice` * (? - `LINEITEM`.`l_discount`)) AS `x` FROM `ORDERS` AS `ORDERS` INNER JOIN `LINEITEM` AS `LINEITEM` ON (((`ORDERS`.`o_orderkey` = `LINEITEM`.`l_orderkey`) AND (`ORDERS`.`o_orderdate` < ?)) AND (`LINEITEM`.`l_shipdate` > ?))")
    Gather(concurrent=true)
      LogicalView(tables="CUSTOMER_[0-7]", shardCount=8, sql="SELECT `c_custkey` FROM `CUSTOMER` AS `CUSTOMER` WHERE (`c_mktsegment` = ?)")

次の図に示すように、LogicalView 演算子の SQL は実行のために MySQL にプッシュダウンされます。PolarDB-X 1.0 のクエリエグゼキュータは、残りの演算子 (LogicalView 以外の演算子) を処理して最終結果を生成します。

查询执行器

Volcano モデル

PolarDB-X 1.0 は、他の多くのデータベースと同様に、Volcano モデルを使用しています。すべての演算子は、open()next() などのメソッドを持つインターフェイスを実装しています。実行計画に基づいて、演算子はツリーを形成します。演算子は、子演算子の next() メソッドを呼び出してデータをプルし、そのデータを処理して、結果を親に渡します。最終的に、ルート演算子が最終的な結果セットを生成し、クライアントに返します。

次の例では、HashJoin 演算子がすでにハッシュテーブルを構築していると仮定します。親の `Project` 演算子がデータをリクエストすると、HashJoin 演算子はまず子である Gather 演算子からデータのバッチをリクエストします。次に、ハッシュテーブルを探索して JOIN の結果を見つけ、それを `Project` 演算子に返します。

Volcano执行模型

場合によっては、演算子はすべての入力データを読み取ってメモリにキャッシュする必要があります。このプロセスはマテリアライズと呼ばれます。例えば、`HashJoin` 演算子は、メモリ内にハッシュテーブルを構築するために、内部テーブルからすべてのデータを読み取る必要があります。マテリアライズを実行する他の演算子には、集約のための `HashAgg` やソートのための `MemSort` があります。

メモリは有限のリソースであるため、マテリアライズされるデータ量がクエリごとの制限を超えたり、総メモリ使用量が PolarDB-X 1.0 ノードの制限を超えたりすると、メモリ不足エラー (OUT_OF_MEMORY) が発生します。

パラレルクエリ

パラレルクエリは、複数のスレッドを使用して単一の複雑なクエリを実行します。

説明 この機能は、PolarDB-X 1.0 の Standard Edition と Enterprise Edition でのみ利用可能です。Starter Edition では、ハードウェアの制限により利用できません。

パラレルクエリの実行計画は、標準の計画とは異なります。例えば、前と同じクエリを使用した場合、パラレル実行計画は次のようになります。

Gather(parallel=true)
  ParallelHashAgg(group="o_orderdate,o_shippriority,l_orderkey", revenue="SUM(*)")
    ParallelHashJoin(condition="o_custkey = c_custkey", type="inner")
      LogicalView(tables="ORDERS_[0-7],LINEITEM_[0-7]", shardCount=8, sql="SELECT `ORDERS`.`o_custkey`, `ORDERS`.`o_orderdate`, `ORDERS`.`o_shippriority`, `LINEITEM`.`l_orderkey`, (`LINEITEM`.`l_extendedprice` * (? - `LINEITEM`.`l_discount`)) AS `x` FROM `ORDERS` AS `ORDERS` INNER JOIN `LINEITEM` AS `LINEITEM` ON (((`ORDERS`.`o_orderkey` = `LINEITEM`.`l_orderkey`) AND (`ORDERS`.`o_orderdate` < ?)) AND (`LINEITEM`.`l_shipdate` > ?))", parallel=true)
      LogicalView(tables="CUSTOMER_[0-7]", shardCount=8, sql="SELECT `c_custkey` FROM `CUSTOMER` AS `CUSTOMER` WHERE (`c_mktsegment` = ?)", parallel=true)
并行查询

この計画では、`Gather` 演算子はツリーの上位に配置されます。これは、その下のすべての演算子が並列で実行されることを意味します。その後、`Gather` 演算子が結果をマージします。

実行中、`Gather` の下の演算子は複数回インスタンス化され、各インスタンスが 1 つの並列度に対応します。デフォルトの並列度は、単一マシンの CPU コア数と同じです。Standard Edition インスタンスの場合、デフォルトの並列度は 8 で、Enterprise Edition インスタンスの場合は 16 です。

実行診断

`EXPLAIN` コマンドに加えて、次のコマンドを使用してパフォーマンスの問題を分析できます。

  • EXPLAIN ANALYZE コマンドは、PolarDB-X 1.0 サーバー内の各演算子のパフォーマンスメトリクスを分析します。
  • EXPLAIN EXECUTE コマンドは、MySQL から集約された EXPLAIN 結果を返します。

次の例では、前のクエリを使用してそのパフォーマンスを分析する方法を示します。

EXPLAIN ANALYZE を実行すると、次の出力が得られます。明確にするために、一部の重要でない情報は削除されています。

explain analyze select l_orderkey, sum(l_extendedprice *(1 - l_discount)) as revenue from CUSTOMER, ORDERS, LINEITEM where c_mktsegment = 'AUTOMOBILE' and c_custkey = o_custkey and l_orderkey = o_orderkey and o_orderdate < '1995-03-13' and l_shipdate > '1995-03-13' group by l_orderkey;
HashAgg(group="o_orderdate,o_shippriority,l_orderkey", revenue="SUM(*)")
... actual time = 23.916 + 0.000, actual rowcount = 11479, actual memory = 1048576, instances = 1 ...
  HashJoin(condition="o_custkey = c_custkey", type="inner")
  ... actual time = 0.290 + 23.584, actual rowcount = 30266, actual memory = 1048576, instances = 1 ...
    Gather(concurrent=true)
    ... actual time = 0.000 + 23.556, actual rowcount = 151186, actual memory = 0, instances = 1 ...
      LogicalView(tables="ORDERS_[0-7],LINEITEM_[0-7]", shardCount=8, sql="SELECT `ORDERS`.`o_custkey`, `ORDERS`.`o_orderdate`, `ORDERS`.`o_shippriority`, `LINEITEM`.`l_orderkey`, (`LINEITEM`.`l_extendedprice` * (? - `LINEITEM`.`l_discount`)) AS `x` FROM `ORDERS` AS `ORDERS` INNER JOIN `LINEITEM` AS `LINEITEM` ON (((`ORDERS`.`o_orderkey` = `LINEITEM`.`l_orderkey`) AND (`ORDERS`.`o_orderdate` < ?)) AND (`LINEITEM`.`l_shipdate` > ?))")
      ... actual time = 0.000 + 23.556, actual rowcount = 151186, actual memory = 0, instances = 4 ...
    Gather(concurrent=true)
    ... actual time = 0.000 + 0.282, actual rowcount = 29752, actual memory = 0, instances = 1 ...
      LogicalView(tables="CUSTOMER_[0-7]", shardCount=8, sql="SELECT `c_custkey` FROM `CUSTOMER` AS `CUSTOMER` WHERE (`c_mktsegment` = ?)")
      ... actual time = 0.000 + 0.282, actual rowcount = 29752, actual memory = 0, instances = 4 ...

出力のメトリクスは次のように定義されます。

  • actual time:演算子とその子の実行に費やされた実際の時間。プラス記号 (+) の前の値は `open` フェーズ (データの準備) の時間で、後の値は `next` フェーズ (データの生成) の時間です。
  • actual rowcount:演算子によって生成された行数。
  • actual memory:演算子が使用したメモリ量 (バイト単位)。
  • instances:演算子インスタンスの数。非パラレルクエリの場合、この値は常に 1 です。パラレルクエリの場合、各並列度が 1 つのインスタンスを表します。インスタンス数が 1 より大きい場合、actual timeactual rowcount、および actual memory は、すべての並列インスタンスにわたる合計時間、合計行数、および合計メモリ使用量を表します。
説明 パラレルクエリが実行されると、実行時間や行数などのメトリクスは、演算子のすべてのインスタンスから集約されます。例えば、actual time = 20,instances = 8 は、演算子の 8 つのインスタンスが並列で実行され、インスタンスあたりの平均実行時間が 2.5 秒であったことを意味します。

出力は次のように解釈できます。

  • `HashAgg` 演算子は、`open` フェーズで 23.916 秒をかけて子である `HashJoin` 演算子から出力をフェッチし、すべてのデータをグループ化して集約しました。このうち、23.601 秒が子からのデータフェッチに費やされ、グループ化と集約には約 0.3 秒しか使用されませんでした。
  • `HashJoin` 演算子は、`open` フェーズで 0.290 秒をかけて右テーブル (下側の `Gather`) からデータをプルし、ハッシュテーブルを構築しました。`next` フェーズでは 23.584 秒をかけて左テーブルからデータをプルし、ハッシュテーブルを探索して JOIN 結果を生成しました。
  • `Gather` 演算子は複数の結果セットをマージするだけなので、そのコストは通常低いです。
  • 左側の `LogicalView` (計画の上側) は、データのフェッチに 23.556 秒かかりました。これにより、これがパフォーマンスボトルネックであると特定されます。
  • 右側の `LogicalView` (下側) は、データのフェッチに 0.282 秒かかりました。

要約すると、パフォーマンスボトルネックは左側の `LogicalView` です。実行計画は、この演算子が `ORDERS` テーブルと `LINEITEM` テーブルに対して JOIN クエリを実行し、このクエリの MySQL での実行が遅いことを示しています。

次の EXPLAIN EXECUTE 文を実行して、MySQL からの EXPLAIN 結果を表示できます。

| id | select_type | table    | partitions | type | possible_keys | key     | key_len | ref                         | rows   | filtered | Extra       |
| 1  | SIMPLE      | ORDERS   | NULL       | ALL  | PRIMARY       | NULL    | NULL    | NULL                        | 184795 | 33.33    | Using where |
| 1  | SIMPLE      | LINEITEM | NULL       | ref  | PRIMARY       | PRIMARY | 4       | qimu_0000.ORDERS.o_orderkey | 3      | 33.33    | Using where |
| 1  | SIMPLE      | CUSTOMER | NULL       | ALL  | NULL          | NULL    | NULL    | NULL                        | 18736  | 10.0     | Using where |
3 rows in set (0.17 sec)

結果では、最初の 2 行 (`ORDERS` と `LINEITEM`) が左側の `LogicalView` にプッシュダウンされたクエリを表し、3 行目 (`CUSTOMER`) が右側の `LogicalView` のクエリを表します。