クエリ実行演算子

はじめに

このページでは、Spanner のクエリ実行プランで使用する演算子について詳しく説明します。Google Cloud コンソールを使用して特定のクエリの実行プランを取得する方法については、Spanner によるクエリの実行方法を理解するをご覧ください。

このページのクエリと実行プランは、次のデータベース スキーマに基づいています。

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
  BirthDate  DATE
) PRIMARY KEY(SingerId);

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

CREATE TABLE Albums (
  SingerId        INT64 NOT NULL,
  AlbumId         INT64 NOT NULL,
  AlbumTitle      STRING(MAX),
  MarketingBudget INT64
) PRIMARY KEY(SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);

CREATE TABLE Songs (
  SingerId  INT64 NOT NULL,
  AlbumId   INT64 NOT NULL,
  TrackId   INT64 NOT NULL,
  SongName  STRING(MAX),
  Duration  INT64,
  SongGenre STRING(25)
) PRIMARY KEY(SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC), INTERLEAVE IN Albums;

CREATE INDEX SongsBySongName ON Songs(SongName);

CREATE TABLE Concerts (
  VenueId      INT64 NOT NULL,
  SingerId     INT64 NOT NULL,
  ConcertDate  DATE NOT NULL,
  BeginTime    TIMESTAMP,
  EndTime      TIMESTAMP,
  TicketPrices ARRAY<INT64>
) PRIMARY KEY(VenueId, SingerId, ConcertDate);

次のデータ操作言語(DML)ステートメントを使用して、これらのテーブルにデータを追加できます。

INSERT INTO Singers (SingerId, FirstName, LastName, BirthDate)
VALUES (1, "Marc", "Richards", "1970-09-03"),
       (2, "Catalina", "Smith", "1990-08-17"),
       (3, "Alice", "Trentor", "1991-10-02"),
       (4, "Lea", "Martin", "1991-11-09"),
       (5, "David", "Lomond", "1977-01-29");

INSERT INTO Albums (SingerId, AlbumId, AlbumTitle)
VALUES (1, 1, "Total Junk"),
       (1, 2, "Go, Go, Go"),
       (2, 1, "Green"),
       (2, 2, "Forever Hold Your Peace"),
       (2, 3, "Terrified"),
       (3, 1, "Nothing To Do With Me"),
       (4, 1, "Play");

INSERT INTO Songs (SingerId, AlbumId, TrackId, SongName, Duration, SongGenre)
VALUES (2, 1, 1, "Let's Get Back Together", 182, "COUNTRY"),
       (2, 1, 2, "Starting Again", 156, "ROCK"),
       (2, 1, 3, "I Knew You Were Magic", 294, "BLUES"),
       (2, 1, 4, "42", 185, "CLASSICAL"),
       (2, 1, 5, "Blue", 238, "BLUES"),
       (2, 1, 6, "Nothing Is The Same", 303, "BLUES"),
       (2, 1, 7, "The Second Time", 255, "ROCK"),
       (2, 3, 1, "Fight Story", 194, "ROCK"),
       (3, 1, 1, "Not About The Guitar", 278, "BLUES");

リーフ演算子

リーフ演算子は子のない演算子です。リーフ演算子には次の種類があります。

配列フラット化

配列フラット化演算子は、入力配列を要素の行にフラット化します。各行は、配列の実際の値が入る列と配列内での位置(先頭は 0)が入る列から構成されます。配列内の位置を表す列はない場合もあります。

たとえば、次のクエリについて考えます。

SELECT a, b FROM UNNEST([1,2,3]) a WITH OFFSET b;

このクエリは、配列 [1,2,3] の値を列 a にフラット化し、配列内での位置を列 b に示します。

結果は次のようになります。

a b
1 0
2 1
3 2

実行プランは次のとおりです。

配列フラット化演算子

生成関係

生成関係演算子は 0 個以上の行を返します。

単位関係

単位関係は 1 行を返します。これは、生成関係演算子の特殊なケースです。

たとえば、次のクエリについて考えます。

SELECT 1 + 2 AS Result;

結果は次のとおりです。

結果
3

実行プランは次のとおりです。

単位関係演算子

空の関係

空の関係は行を返しません。これは、生成関係演算子の特殊なケースです。

たとえば、次のクエリについて考えます。

SELECT *
FROM   albums
LIMIT  0

結果は次のとおりです。

該当なし

実行プランは次のとおりです。

空の関係演算子

スキャン

スキャン演算子は、行の取得元をスキャンして行を返します。スキャン演算子の種類は次のとおりです。

  • テーブル スキャン: テーブルをスキャンします。
  • インデックス スキャン: インデックスをスキャンします。
  • バッチスキャン: 他の関係演算子が作成した中間テーブル(たとえば、分散クロス適用によって作成されたテーブル)をスキャンします。

可能であれば、Spanner はスキャン中にキーに述語を適用します。述語を適用すると、テーブルまたはインデックス全体を読み取る必要がなくなるため、スキャンが効率的に実行されます。実行プランで、述語は KeyPredicate: column=value という形式で記述されます。

最悪のケースでは、クエリでテーブルのすべての行の検索が必要になる場合があります。この状況では、フルスキャンが実行され、実行プランに full scan: true と表示されます。

たとえば、次のクエリについて考えます。

SELECT s.lastname
FROM   singers@{FORCE_INDEX=SingersByFirstLastName} as s
WHERE  s.firstname = 'Catalina';

結果は次のようになります。

LastName
Smith

実行プランは次のとおりです。

スキャン演算子

実行プランで、最上位の分散ユニオン演算子がリモート サーバーにサブプランを送信します。各サブプランには、結果のシリアル化演算子とインデックス スキャン演算子が含まれます。述語 Key Predicate: FirstName = 'Catalina' は、スキャンの範囲をインデックス SingersByFirstLastnameFirstNameCatalina の行に限定します。インデックス スキャンの出力が結果のシリアル化演算子に返されます。

単項演算子

単項演算子は、関連する子が 1 つしかない演算子です。

次の演算子が単項演算子になります。

集計

集計演算子は、GROUP BY SQL ステートメントと集計関数(COUNT など)を実装します。集計演算子の入力は論理的に分割され、キー列でグループ化されます。GROUP BY が存在しない場合、単一のグループに配置されます。各グループで、ゼロ個以上の集計が計算されます。

たとえば、次のクエリについて考えます。

SELECT s.singerid,
       Avg(s.duration) AS average,
       Count(*)        AS count
FROM   songs AS s
GROUP  BY singerid;

このクエリは、SingerId でグループ化し、AVG 集計と COUNT 集計を実行します。

結果は次のようになります。

SingerId 平均 count
3 278 1
2 225.875 8

実行プランは次のとおりです。

集計演算子

集計演算子はストリームベースまたはハッシュベースになります。上の実行プランでは、ストリームベースの集計になっています。ストリームベースの集計は、並べ替え済みの入力(GROUP BY がある場合)から読み取り、ブロック化を行わずにグループを計算します。ハッシュベースの集計は、ハッシュ テーブルを作成し、複数の入力行の増分集計を同時に維持します。ハッシュベースの集計よりもストリームベースの集計のほうが高速で、メモリの使用量も少なくなりますが、キー列またはセカンダリ インデックスのいずれかを使用して入力の並べ替えが必要になります。

分散シナリオでは、集計演算子をローカルとグローバルに分割できます。それぞれのリモート サーバーがローカルで入力行に集計を実行し、結果をルートサーバーに返します。全体の集計はルートサーバーで行います。

ミューテーション適用

ミューテーション適用演算子は、データ操作ステートメント(DML)のミューテーションをテーブルに適用します。これは、DML ステートメントのクエリプランの最上位演算子です。

たとえば、次のクエリについて考えます。

DELETE FROM singers
WHERE  firstname = 'Alice';

結果は次のようになります。

 4 rows deleted  This statement deleted 4 rows and did not return any rows.

実行プランは次のとおりです。

ミューテーション適用演算子

バッチ作成

バッチ作成演算子は、入力行を 1 つのシーケンスにまとめます。バッチ作成オペレーションは通常、分散クロス適用の中で行われます。バッチ作成時に入力行の並べ替えが再度実行されます。バッチ演算子の 1 回ので実行で処理される入力行の数は一定ではありません。

実行プランのバッチ作成演算子の例については、分散クロス適用演算子をご覧ください。

コンピューティング

計算演算子は、入力行を読み取り、スカラー式で計算される列を追加して結果を生成します。実行プランでの計算演算子の例については、UNION ALL 演算子をご覧ください。

構造体計算

構造体計算演算子は、入力列のフィールドを含む構造体の変数を作成します。

たとえば、次のクエリについて考えます。

SELECT FirstName,
       ARRAY(SELECT AS STRUCT song.SongName, song.SongGenre
             FROM Songs AS song
             WHERE song.SingerId = singer.SingerId)
FROM singers AS singer
WHERE singer.SingerId = 3;

結果は次のようになります。

FirstName 指定なし
Alice [["Not About The Guitar","BLUES"]]

実行プランは次のとおりです。

構造体計算演算子

実行プランでは、配列サブクエリ演算子が分散ユニオン演算子から入力を受信しています。分散ユニオン演算子は、構造体計算演算子から入力を受信しています。構造体計算演算子は、Songs テーブルの SongName 列と SongGenre 列から構造体を作成します。

フィルタ

フィルタ演算子は、入力からすべての行を読み取り、各行にスカラー述語を適用して、述語の条件を満たす行のみを返します。

たとえば、次のクエリについて考えます。

SELECT s.lastname
FROM   (SELECT s.lastname
        FROM   singers AS s
        LIMIT  3) s
WHERE  s.lastname LIKE 'Rich%';

結果は次のようになります。

LastName
Richards

実行プランは次のとおりです。

フィルタ演算子

名前が Rich で始まる歌手を条件とする述語がフィルタとして実装されています。フィルタの入力はインデックス スキャンからの出力です。フィルタの出力は LastNameRich で始まる行です。

フィルタがスキャンのすぐ上にある場合、フィルタによりデータの読み取り方法が変わります。たとえば、キー k を持つテーブルについて考えてみましょう。述語 k = 5 を持つフィルタがテーブル スキャンのすぐ上にあるため、入力全体の読み取りを行わず、k = 5 に一致する行を検索します。このため、クエリの実行効率がよくなります。上の例では、フィルタ演算子は述語 WHERE s.LastName LIKE 'Rich%' を満たす行しか読み取りません。

フィルタ スキャン

フィルタ スキャン演算子は、常にテーブル スキャンまたはインデックス スキャンよりも上位で実行されます。スキャンとの連携により、データベースから読み込まれる行数が少なくなります。結果として、フィルタを使用する場合よりもスキャンが高速になります。Spanner は特定の条件でフィルタ スキャンを適用します。

  • シーク可能な条件: この条件は、Spanner がテーブル内でアクセスする行を特定したときに適用されます。これは通常、フィルタが主キーのプレフィックスにある場合に発生します。たとえば、主キーが Col1Col2 で構成されている場合、Col1 または Col1Col2 に明示的な値を含む WHERE 句がシーク可能になります。この場合、Spanner はキー範囲内のデータのみを読み取ります。
  • 残存条件: Spanner がスキャン結果を評価し、読み取るデータの量を制限する条件。

たとえば、次のクエリについて考えます。

SELECT lastname
FROM   singers
WHERE  singerid = 1

結果は次のようになります。

LastName
Richards

実行プランは次のとおりです。

フィルタ スキャン演算子

上限

制限演算子は、返される行数を制限します。OFFSET パラメータに開始行を指定します(このパラメータは省略可能です)。分散シナリオでは、制限演算子をローカルとグローバルに分割できます。各リモート サーバーが出力行にローカルの制限を適用し、結果をルートサーバーに返します。ルートサーバーは、リモート サーバーから受信した行を集計し、全体の制限を適用します。

たとえば、次のクエリについて考えます。

SELECT s.songname
FROM   songs AS s
LIMIT  3;

結果は次のようになります。

SongName
Not About The Guitar
The Second Time
Starting Again

実行プランは次のとおりです。

制限演算子

ローカルの制限は、それぞれのリモート サーバーの制限です。ルートサーバーは、リモート サーバーから受信した行を集計し、全体の制限を適用します。

ランダムな ID の割り当て

ランダムな ID の割り当て演算子は、入力行を読み取り、各行に乱数を追加することで出力を生成します。サンプリング方法を実現するには Filter または Sort 演算子を使用します。サポートされているサンプリング方法は BernoulliReservoir です。

たとえば、次のクエリは、サンプリング レートが 10% の Bernoulli サンプリングを使用しています。

SELECT s.songname
FROM   songs AS s TABLESAMPLE bernoulli (10 PERCENT);

結果は次のようになります。

SongName
Starting Again
Nothing Is The Same

この結果はサンプルで、クエリを実行するたびに変わります。同じクエリを実行しても変わる場合があります。

実行プランは次のとおりです。

bernoulli サンプル演算子

この実行プランでは、Random Id Assign 演算子が分散ユニオン演算子から入力を受信します。分散ユニオン演算子はインデックス スキャンから入力を受信します。演算子はランダムな ID を持つ行を返します。Filter 演算子はランダムな ID にスカラー述語を適用し、行の約 10% を返します。

次の例では、サンプリング レートが 2 行の Reservoir サンプリングを使用しています。

SELECT s.songname
FROM   songs AS s TABLESAMPLE reservoir (2 rows);

結果は次のようになります。

SongName
I Knew You Were Magic
The Second Time

この結果はサンプルで、クエリを実行するたびに変わります。同じクエリを実行しても変わる場合があります。

実行プランは次のとおりです。

Reservoir サンプル演算子

この実行プランでは、Random Id Assign 演算子が分散ユニオン演算子から入力を受信します。分散ユニオン演算子はインデックス スキャンから入力を受信します。演算子はランダムな ID を持つ行を返し、Sort 演算子はランダムな ID に並べ替え順序を適用し、LIMIT に 2 行を適用します。

ローカル分割ユニオン

ローカル分割ユニオン演算子は、ローカル サーバーに保存されているテーブルのスプリットを見つけ、各スプリットに対してサブクエリを実行し、すべての結果を結合するユニオンを作成します。

ローカル分割ユニオンは、プレースメント テーブルをスキャンする実行プランに表示されます。プレースメントを使用すると、テーブル内のスプリット数を増やすことができます。これにより、物理ストレージ ロケーションに基づいてスプリットを一括でスキャンすることがより効率的になります。

たとえば、Singers テーブルでプレースメント キーを使用して歌手データをパーティショニングするとします。

CREATE TABLE Singers (
    SingerId INT64 NOT NULL,
    SingerName STRING(MAX) NOT NULL,
    ...
    Location STRING(MAX) NOT NULL PLACEMENT KEY
) PRIMARY KEY (SingerId);

次のクエリについて考えてみましょう。

SELECT BirthDate FROM Singers;

実行プランは次のとおりです。

ローカル分割結合演算子

分散ユニオンは、同じサーバーに物理的に一緒に保存されているスプリットのバッチごとにサブクエリを送信します。各サーバーで、ローカル スプリット ユニオンSingers データを保存するスプリットを見つけ、各スプリットに対してサブクエリを実行し、結合結果を返します。このように、分散ユニオンとローカル分割ユニオンが連携して、Singers テーブルを効率的にスキャンします。ローカル分割ユニオンがない場合、分散ユニオンは分割バッチごとにではなく、分割ごとに 1 つの RPC を送信します。これにより、バッチごとに複数の分割がある場合は、RPC ラウンドトリップが冗長になります。

結果のシリアル化

結果のシリアル化演算子は構造体演算子の特別なケースで、クライアントに返す前にクエリの最終結果の各行をシリアル化します。

たとえば、次のクエリについて考えます。

SELECT array
  (
    select as struct so.songname,
            so.songgenre
    FROM   songs AS so
    WHERE  so.singerid = s.singerid)
FROM  singers AS s;

このクエリは、SingerId に基づいて SongNameSongGenre の配列を取得します。

結果は次のようになります。

指定なし
[]
[[Let's Get Back Together, COUNTRY], [Starting Again, ROCK]]
[[Not About The Guitar, BLUES]]
[]
[]

実行プランは次のとおりです。

結果のシリアル化演算子

結果のシリアル化演算子は、Singersテーブルの各行に、曲のSongNameSongGenreのペアの配列を歌手ごとに含む結果を作成します。

並べ替え

並べ替え演算子は、入力行を読み取り、列で並べ替えて結果を返します。

たとえば、次のクエリについて考えます。

SELECT s.songgenre
FROM   songs AS s
ORDER  BY songgenre;

結果は次のようになります。

SongGenre
BLUES
BLUES
BLUES
BLUES
CLASSICAL
ROCK
ROCK
ROCK

実行プランは次のとおりです。

並べ替え演算子

この実行プランでは、並べ替え演算子は分散ユニオン演算子から入力行を受け取り、入力行を並べ替えてから結果のシリアル化演算子に返します。

返す行数を制限するには、並べ替え演算子に LIMIT パラメータと OFFSET パラメータを指定します。これらのパラメータは省略可能です。分散シナリオでは、LIMIT または OFFSET 演算子を使用した並べ替え演算子はローカルとグローバルに分かれます。各リモート サーバーが入力行に並べ替え順とローカルの制限またはオフセットを適用し、結果をルートサーバーに返します。ルートサーバーは、リモート サーバーから受信した行を集計して並べ替え、全体の制限 / オフセットを適用します。

たとえば、次のクエリについて考えます。

SELECT s.songgenre
FROM   songs AS s
ORDER  BY songgenre
LIMIT  3;

結果は次のようになります。

SongGenre
BLUES
BLUES
BLUES

実行プランは次のとおりです。

並べ替え演算子と制限

この実行プランは、リモート サーバーのローカル制限とルートサーバーの全体制限を表しています。

TVF

テーブル値関数演算子は、入力行を読み取り、指定された関数を適用することで出力を生成します。この関数はマッピングを実装し、入力と同じ数の行を返す場合があります。行数を増やすジェネレータや、行数を減らすフィルタにすることもできます。

たとえば、次のクエリについて考えます。

SELECT genre,
       songname
FROM   ml.predict(model genreclassifier, TABLE songs)

結果は次のようになります。

ジャンル SongName
カントリー Not About The Guitar
ロック The Second Time
ポップ Starting Again
ポップ Nothing Is The Same
カントリー Let's Get Back Together
ポップ I Knew You Were Magic
電子音楽
ロック 42
ロック Fight Story

実行プランは次のとおりです。

tvf 演算子

ユニオン入力

ユニオン入力演算子は、結果を UNION ALL 演算子に返します。実行プランでのユニオン入力演算子の例については、UNION ALL 演算子を参照してください。

2 項演算子

2 項演算子は、関係する子が 2 つある演算子です。次の演算子が 2 項演算子になります。

クロス適用

クロス適用演算子は、別のテーブルのクエリで取得した各行にテーブルクエリを実行し、テーブルクエリの結果をすべて結合して返します。クロス適用演算子と外部適用演算子は行単位で処理を行いますが、ハッシュ結合などの演算子はセット単位で処理を行います。クロス適用演算子には、入力とマップという 2 つの入力があります。クロス適用演算子は、入力側の各行をマップ側に適用します。クロス適用の結果には、入力側とマップ側の両方の列が含まれます。

たとえば、次のクエリについて考えます。

 SELECT si.firstname,
       (SELECT so.songname
        FROM   songs AS so
        WHERE  so.singerid = si.singerid
        LIMIT  1)
FROM   singers AS si;

このクエリは、歌手の名前とその歌手の曲名の 1 つを取得します。

結果は次のようになります。

FirstName 指定なし
Alice Not About The Guitar
Catalina Let's Get Back Together
David NULL
Lea NULL
Marc NULL

最初の列は Singers テーブルから取得し、2 番目の列は Songs テーブルから取得しています。SingerIdSingers テーブルに存在していますが、Songs テーブルに該当する SingerId が存在しないため、2 番目の列に NULL が入っています。

実行プランは次のとおりです。

クロス適用演算子

最上位ノードは分散ユニオン演算子です。分散ユニオン演算子は、サブプランをリモート サーバーに分散します。サブプランに含まれている結果のシリアル化演算子が歌手の名前とその歌手の曲名を計算し、出力の各行にまとめて出力します。

結果のシリアル化演算子は、クロス適用演算子から入力を受信します。クロス適用演算子の入力側は Singers テーブルのテーブル スキャンです。

クロス適用演算のマップ側には、次のものが上から順に含まれます。

  • 集計演算子。Songs.SongName を返します。
  • 制限演算子。1 人の歌手につき、返される曲数を 1 に制限しています。
  • SongsBySingerAlbumSongNameDesc インデックスのインデックス スキャン

クロス適用演算子は、入力側のマップとマップ側で SingerId が同じ行を関連付けます。クロス適用演算子の出力は、入力行からの FirstName 値とマップ行からの SongName 値になります。SingerId に一致するマップ行がない場合、SongName 値は NULL になります。実行プランの最上位にある分散ユニオン演算子は、リモート サーバーからの出力行をすべて結合し、クエリ結果として返します。

ハッシュ結合

ハッシュ結合演算子はハッシュベースで行う SQL 結合です。ハッシュ結合はセット単位の処理を実行します。ハッシュ結合演算子は、build とマークされた入力行を読み取り、結合条件に基づいてハッシュ テーブルに挿入します。次に、probe とマークされた行を読み取ります。ハッシュ結合演算子は、プローブ入力から読み取った各行に対して、ハッシュ テーブル内で一致する行を探します。一致した行を結果として返します。

たとえば、次のクエリについて考えます。

SELECT a.albumtitle,
       s.songname
FROM   albums AS a join@{join_method=hash_join} songs AS s
ON a.singerid = s.singerid
AND    a.albumid = s.albumid;

結果は次のようになります。

AlbumTitle SongName
Nothing To Do With Me Not About The Guitar
The Second Time
Starting Again
Nothing Is The Same
Let's Get Back Together
I Knew You Were Magic
42
Terrified Fight Story

実行プランは次のとおりです。

ハッシュ結合演算子

この実行プランで、ビルドはテーブル Albums に対するスキャンを分散する分散ユニオンです。Probe は、インデックス SongsBySingerAlbumSongNameDesc に対するスキャンを分散する分散ユニオン演算子です。ハッシュ結合演算子がビルド側からすべての行を読み取ります。各ビルド行が、条件 a.SingerId = s.SingerId AND a.AlbumId = s.AlbumId の列に基づいてハッシュ テーブルに配置されます。次に、ハッシュ結合演算子はプローブ側からすべての行を読み取ります。各プローブ行に対して、ハッシュ結合演算子はハッシュ テーブル内で一致する行を探し、一致した結果を返します。

ハッシュ テーブルで一致した結果は、残余条件でフィルタリングされてから返される場合があります。(たとえば、非等値結合に残余条件がある場合)。ハッシュ結合の実行プランは、メモリ管理や結合が一定でないため、複雑になる可能性があります。メインのハッシュ結合アルゴリズムが内部結合、半結合、反結合、外部結合の変数処理に使用されます。

マージ結合

マージ結合演算子はマージベースで行う SQL 結合です。結合の両側は、結合条件で使用される列の順に並んだ行を生成します。マージ結合は、両方の入力ストリームを並行して使用し、結合条件が満たされると行を出力します。入力の元々の並び順が必要に応じたものでない場合、オプティマイザーは明示的に Sort 演算子をプランに追加します。

マージ結合は、オプティマイザーによって自動的に選択されることはありません。この演算子を使用するには、次の例に示すように、クエリヒントの結合メソッドを MERGE_JOIN に設定します。

SELECT a.albumtitle,
       s.songname
FROM   albums AS a join@{join_method=merge_join} songs AS s
ON     a.singerid = s.singerid
AND    a.albumid = s.albumid;

結果は次のようになります。

AlbumTitle SongName
The Second Time
Starting Again
Nothing Is The Same
Let's Get Back Together
I Knew You Were Magic
42
Terrified Fight Story
Nothing To Do With Me Not About The Guitar

実行プランは次のとおりです。

マージ結合演算子_1

この実行プランでは、データが存在する場所で結合が行われるようにマージ結合が分散されます。また、両方のテーブル スキャンはあらかじめ結合条件の SingerIdAlbumIdによってソートされるため、この例のマージ結合は、追加の並べ替え演算子を導入しなくても動作できます。このプランでは、Albums テーブルの左側のスキャンは、左側の SingerIdAlbumId が、右側の SongsBySingerAlbumSongNameDesc インデックス スキャンの SingerId_1AlbumId_1 のペアよりも比較的少ない場合に進みます。同様に、右側が左側より少ない場合、右側のスキャンが進みます。こうして、結果的に一致するものが返すことができるよう、等値を検索し続ける形でマージが進みます。

次のクエリを使用して、マージ結合の例を考えてみましょう。

SELECT a.albumtitle,
       s.songname
FROM   albums AS a join@{join_method=merge_join} songs AS s
ON a.albumid = s.albumid;

結果は次のようになります。

AlbumTitle SongName
Total Junk The Second Time
Total Junk Starting Again
Total Junk Nothing Is The Same
Total Junk Let's Get Back Together
Total Junk I Knew You Were Magic
Total Junk
Total Junk 42
Total Junk Not About The Guitar
The Second Time
Starting Again
Nothing Is The Same
Let's Get Back Together
I Knew You Were Magic
42
Not About The Guitar
Nothing To Do With Me The Second Time
Nothing To Do With Me Starting Again
Nothing To Do With Me Nothing Is The Same
Nothing To Do With Me Let's Get Back Together
Nothing To Do With Me I Knew You Were Magic
Nothing To Do With Me
Nothing To Do With Me 42
Nothing To Do With Me Not About The Guitar
Play The Second Time
Play Starting Again
Play Nothing Is The Same
再生 Let's Get Back Together
再生 I Knew You Were Magic
Play
Play 42
Play Not About The Guitar
Terrified Fight Story

実行プランは次のとおりです。

マージ結合演算子_2

以前の実行プランでは、マージ結合に必要なプロパティを実現するため、クエリ オプティマイザーによって追加の Sort 演算子を導入していました。この例のクエリの JOIN 条件は AlbumId の条件のみでデータの格納方法ではありません。よって、並べ替えを追加する必要があります。クエリエンジンは分散マージ アルゴリズムをサポートしており、並べ替えをグローバルではなくローカルで実行することで、CPU コストを分散して並列化します。

一致した結果は、残余条件でフィルタリングされてから返される場合があります(たとえば、非等値結合に残余条件がある場合)。マージ結合プランは、追加の並べ替え要件が原因で複雑になる場合があります。メインのマージ結合アルゴリズムが内部結合、半結合、反結合、外部結合の変数の処理に使用されます。

プッシュ ブロードキャストのハッシュ結合

プッシュ ブロードキャストのハッシュ結合演算子は、分散ハッシュ結合ベースで行う SQL 結合です。プッシュ ブロードキャストのハッシュ結合演算子は、入力側から行を読み取り、データのバッチを作成します。このバッチは、マップ側のデータを格納するすべてのサーバーにブロードキャストされます。データのバッチを受信する宛先サーバーでは、バッチをビルド側のデータとして使用してハッシュ結合を構築した後、ハッシュ結合のプローブ側としてローカルデータをスキャンします。

オプティマイザーでは、プッシュ ブロードキャストのハッシュ結合は自動的には選択されません。この演算子を使用するには、次の例に示すように、クエリヒントの結合メソッドを PUSH_BROADCAST_HASH_JOIN に設定します。

SELECT a.albumtitle,
       s.songname
FROM   albums AS a join@{join_method=push_broadcast_hash_join} songs AS s
ON     a.singerid = s.singerid
AND    a.albumid = s.albumid;

結果は次のようになります。

AlbumTitle SongName
The Second Time
Starting Again
Nothing Is The Same
Lets Get Back Together
I Knew You Were Magic
42
Terrified Fight Story
Nothing To Do With Me Not About The Guitar

実行プランは次のとおりです。

push_broadcast hash_join 演算子

プッシュ ブロードキャストのハッシュ結合への入力は AlbumsByAlbumTitle インデックスです。この入力は、データのバッチにシリアル化されます。このバッチは、インデックス SongsBySingerAlbumSongNameDesc のすべてのローカル スプリットに送信されます。バッチはシリアル化解除され、ハッシュ テーブルが作成されます。ハッシュテーブルは、ローカル インデックス データをプローブとして使用し、一致結果を返します。

一致した結果は、残余条件でフィルタリングされてから返される場合があります。(たとえば、非等値結合に残余条件がある場合)。

外部適用

外部適用演算子はクロス適用演算子に似ていますが、マップ側の実行で必要に応じて行に NULL を埋め込むことによって、少なくとも 1 行を返す点が異なります(左外部結合を行います)。

再帰型ユニオン

再帰ユニオン演算子は、2 つの入力の結合を実行します。1 つは base ケースを表し、もう 1 つは recursive ケースを表します。これは、定量化されたパス走査を含むグラフクエリで使用されます。ベース入力は最初に 1 回だけ処理されます。再帰が終了するまで、再帰入力が処理されます。再帰は、上限(指定されている場合)に達するか、再帰で新しい結果が生成されなくなると終了します。次の例では、Collaborations テーブルがスキーマに追加され、MusicGraph というプロパティグラフが作成されます。

CREATE TABLE Collaborations (
    SingerId INT64 NOT NULL,
    FeaturingSingerId INT64 NOT NULL,
    AlbumTitle STRING(MAX) NOT NULL,
) PRIMARY KEY(SingerId, FeaturingSingerId, AlbumTitle);

CREATE OR REPLACE PROPERTY GRAPH MusicGraph
    NODE TABLES(
        Singers
            KEY(SingerId)
            LABEL Singers PROPERTIES(
                BirthDate,
                FirstName,
                LastName,
                SingerId,
                SingerInfo)
            )
EDGE TABLES(
    Collaborations AS CollabWith
        KEY(SingerId, FeaturingSingerId, AlbumTitle)
        SOURCE KEY(SingerId) REFERENCES Singers(SingerId)
        DESTINATION KEY(FeaturingSingerId) REFERENCES Singers(SingerId)
        LABEL CollabWith PROPERTIES(
          AlbumTitle,
          FeaturingSingerId,
          SingerId),
);

次のグラフクエリは、特定の歌手とコラボレーションした歌手、またはそのコラボレーション相手を見つけます。

GRAPH MusicGraph
MATCH (singer:Singers {singerId:42})-[c:CollabWith]->{1,2}(featured:Singers)
RETURN singer.SingerId AS singer, featured.SingerId AS featured

再帰ユニオン演算子

再帰ユニオン演算子は、Singers テーブルをフィルタして、指定された SingerId を持つ歌手を見つけます。これは、再帰ユニオンの基本入力です。再帰結合への再帰入力は、Collaborations テーブルを結合の前の反復処理の結果と繰り返し結合する他のクエリの分散クロス適用または他の結合演算子で構成されます。ベース入力の行が 0 回目の反復処理を形成します。反復処理ごとに、反復処理の出力が再帰スプロール スキャンによって保存されます。再帰スプロール スキャンの行は、spoolscan.featuredSingerId = Collaborations.SingerIdCollaborations テーブルと結合されます。2 回目の反復処理が完了すると、再帰は終了します。これは、クエリで指定された上限です。

N 項演算子

N 項演算子は、関連する子が 3 つ以上ある演算子です。次の演算子が N 項演算子になります。

全体結合

全体結合演算子は、重複を除外せずに子のすべての行セットを結合します。UNION ALL 演算子は、複数のサーバーに分散するユニオン入力演算子から入力を受信します。UNION ALL 演算子を使用する場合、同じスキーマの入力が必要になります。つまり、各列に同じデータ型のセットが含まれている必要があります。

たとえば、次のクエリについて考えます。

SELECT 1 a,
       2 b
UNION ALL
SELECT 3 a,
       4 b
UNION ALL
SELECT 5 a,
       6 b;

子の行は 2 つの整数から構成されています。

結果は次のようになります。

a b
1 2
3 4
5 6

実行プランは次のとおりです。

union_all_operator

全体結合演算子は、入力行を結合して結果のシリアル化演算子に結果を送信します。

次のクエリでは、各列のデータに同じデータ型が使用されているため、子の列名で異なる変数を使用していても処理に成功します。

SELECT 1 a,
       2 b
UNION ALL
SELECT 3 c,
       4 e;

次のクエリでは、子の列で異なるデータ型が使用されているため、処理に失敗します。

SELECT 1 a,
       2 b
UNION ALL
SELECT 3 a,
  'This is a string' b;

スカラー サブクエリ

スカラー サブクエリは、スカラー式に含まれる SQL サブ式です。可能であれば、Spanner はスカラー サブクエリを削除します。実行プランでスカラー サブクエリが明示的に使用されている場合があります。

たとえば、次のクエリについて考えます。

SELECT firstname,
  IF(firstname = 'Alice', (SELECT Count(*)
                          FROM   songs
                          WHERE  duration > 300), 0)
FROM   singers;

SQL サブ式は次のようになります。

SELECT Count(*)
FROM   songs
WHERE  duration > 300;

完全なクエリの結果は次のとおりです。

FirstName
Alice 1
Catalina 0
David 0
Lea 0
Marc 0

実行プランは次のとおりです。

スカラー サブクエリ演算子

この実行プランには、集計演算子の上にスカラー サブクエリがあります(Scalar Subquery として示されています)。

パフォーマンスを改善するため、Spanner がスカラー サブクエリを別の演算子(結合、クロス結合など)に変換する場合があります。

たとえば、次のクエリについて考えます。

SELECT *
FROM   songs
WHERE  duration = (SELECT Max(duration)
                   FROM   songs);

SQL サブ式は次のようになります。

SELECT MAX(Duration)
FROM Songs;

完全なクエリの結果は次のとおりです。

SingerId AlbumId TrackId SongName 所要時間 SongGenre
2 1 6 Nothing Is The Same 303 BLUES

実行プランは次のとおりです。

プランに含まれないスカラー サブクエリ演算子

Spanner がスカラー サブクエリをクロス適用に変更したため、この実行プランにはスカラー サブクエリが含まれていません。

配列サブクエリ

配列サブクエリはスカラー サブクエリに似ていますが、複数の入力行を使用できる点が異なります。使用した行は 1 つのスカラー出力配列に変換され、使用した入力行ごとに 1 つの要素を含む配列が作成されます。

たとえば、次のクエリについて考えます。

SELECT a.albumid,
       array
       (
              select concertdate
              FROM   concerts
              WHERE  concerts.singerid = a.singerid)
FROM   albums AS a;

サブクエリは次のようになります。

SELECT concertdate
FROM   concerts
WHERE  concerts.singerid = a.singerid;

AlbumId のサブクエリの結果が AlbumIdConcertDate 行から構成される配列に変換されます。この実行プランには、分散ユニオン演算子の上に Array Subquery という配列サブクエリがあります。

配列サブクエリ演算子

分散演算子

ここまでに説明した演算子は単一マシンの境界内で実行されます。分散演算子は、複数のサーバー間で実行されます。

次の演算子は分散演算子です。

分散ユニオン演算子はプリミティブ演算子で、この演算子から分散クロス適用、分散外部適用が派生しています。

実行プランでは、1 個以上のローカル分散ユニオン変数の上に分散演算子が分散ユニオン変数と一緒に存在します。分散ユニオン変数は、サブプランのリモート分散を実行します。ローカル分散ユニオン変数は、次の実行プランのように、クエリで実行されるスキャンの上に存在します。

分散演算子

ローカル分散ユニオン変数により、スプリットの境界が動的に変更され、再起動が発生してもクエリが安定して実行されます。

可能であれば、分散ユニオン変数にはスプリットのプルーニングを行う述語が設定されます。リモート サーバーは、述語の条件を満たすスプリットにのみサブプランを実行します。これにより、レイテンシとクエリ全体のパフォーマンスが向上します。

分散ユニオン

概念的には、分散ユニオン演算子は、1 つ以上のテーブルを複数のスプリットに分割し、各スプリットのサブクエリをリモートで個別に評価してから、すべての結果を結合します。

たとえば、次のクエリについて考えます。

SELECT s.songname,
       s.songgenre
FROM   songs AS s
WHERE  s.singerid = 2
       AND s.songgenre = 'ROCK';

結果は次のようになります。

SongName SongGenre
Starting Again ROCK
The Second Time ROCK
Fight Story ROCK

実行プランは次のとおりです。

分散ユニオン演算子

分散ユニオン演算子はリモート サーバーにサブプランを送信します。これにより、スプリット全体にテーブル スキャンを実行し、クエリの述語 WHERE s.SingerId = 2 AND s.SongGenre = 'ROCK' を満たすものを探します。結果のシリアル化演算子は、テーブル スキャンによって返された行から SongNameSongGenre の値を導きます。分散ユニオン演算子は、リモート サーバーから受信し、結合した結果を SQL クエリの結果として返します。

分散マージ ユニオン

分散マージユニオン演算子は、複数のリモート サーバー間でクエリを分散します。その後、クエリ結果を結合して、並べ替えられた結果を生成します。これは、分散マージソートと呼ばれます。

分散マージ ユニオンでは、次の手順が実行されます。

  1. ルートサーバーは、クエリされたデータのスプリットをホストする各リモート サーバーにサブクエリを送信します。サブクエリには、結果を特定の順序で並べ替える指示が含まれています。

  2. 各リモート サーバーは、そのスプリットに対してサブクエリを実行し、リクエストされた順序で結果を返します。

  3. ルートサーバーは、並べ替えられたサブクエリを統合して、完全に並べ替えられた結果を生成します。

Spanner バージョン 3 以降では、分散マージ結合がデフォルトでオンになっています。

分散クロス適用

分散クロス適用(DCA)演算子は、クロス適用 演算子を拡張して複数のサーバー全体で実行します。DCA の入力側が行のバッチをグループ化します。通常のクロス適用演算子と異なり、同時に複数の入力を処理できます。DCA マップ側は、リモート サーバーでクロス適用演算子を実行します。

たとえば、次のクエリについて考えます。

SELECT albumtitle
FROM   songs
       JOIN albums
         ON albums.albumid = songs.albumid;

結果は次の形式になります。

AlbumTitle
Nothing To Do With Me
Play
Total Junk

実行プランは次のとおりです。

分散クロス適用演算子

DCA の入力には、AlbumId の行のバッチを作成する SongsBySingerAlbumSongNameDesc インデックスに対するインデックス スキャンが含まれます。クロス適用演算子のマップ側は、インデックス AlbumsByAlbumTitle に対するインデックス スキャンです。これは、入力行の AlbumId の述語の影響を受け、AlbumsByAlbumTitle インデックスの AlbumId キーと比較されます。マッピングの結果、バッチが作成された入力行の SingerId 値の SongName が返されます。

この例の DCA プロセスを要約すると、DCA の入力は Albums テーブルから抽出された行で、DCA の出力はインデックス スキャンのマップに一致する行になります。

分散外部適用

分散外部適用演算子は、分散クロス適用演算子がクロス適用演算子を拡張するのと同様に、外部適用演算子を拡張して複数のサーバー全体で実行します。

たとえば、次のクエリについて考えます。

SELECT lastname,
       concertdate
FROM   singers LEFT OUTER join@{JOIN_TYPE=APPLY_JOIN} concerts
ON singers.singerid=concerts.singerid;

結果は次の形式になります。

LastName ConcertDate
Trentor 2014-02-18
Smith 2011-09-03
Smith 2010-06-06
Lomond 2005-04-30
Martin 2015-11-04
Richards

実行プランは次のとおりです。

分散外部適用演算子

ミューテーション適用

ミューテーション適用演算子は、データ操作ステートメント(DML)のミューテーションをテーブルに適用します。これは、DML ステートメントのクエリプランの最上位演算子です。

たとえば、次のクエリについて考えます。

DELETE FROM singers
WHERE  firstname = 'Alice';

結果は次のようになります。

 4 rows deleted  This statement deleted 4 rows and did not return any rows.

実行プランは次のとおりです。

ミューテーション適用演算子

その他の情報

次に、単独の演算子ではありませんが、前述の 1 つ以上の演算子をサポートする演算子について説明します。技術的にみると、ここに説明する項目は技術的な演算子ですが、クエリ実行計画では独立した演算子になっていません。

構造体コンストラクタ

構造体コンストラクタは、構造体またはフィールドの集合体を作成します。通常、計算オペレーションで生成された行に構造体を作成します。構造体コンストラクタは、独立した演算子ではありません。常に構造体計算演算子または結果のシリアル化演算子の中で使用されます。

構造体計算の場合、構造体コンストラクタが構造体を作成し、計算された行の列が 1 つの変数で構造体を参照できます。

結果のシリアル化の場合、構造体コンストラクタが結果のシリアル化に使用する構造体を作成します。

たとえば、次のクエリについて考えます。

SELECT IF(TRUE, struct(1 AS A, 1 AS B), struct(2 AS A , 2 AS B)).A;

結果は次のようになります。

A
1

実行プランは次のとおりです。

構造体コンストラクタ

この実行プランでは、構造体コンストラクタが結果のシリアル化演算子の中で使用されています。