Cloud Spanner データベースでは、Cloud Spanner により、テーブルの主キー列ごとに自動的にインデックスが作成されます。たとえば、Singers
の主キー列は自動的にインデックスに登録されるため、操作は必要はありません。
他の列のセカンダリ インデックスを作成することもできます。セカンダリ インデックスを列に追加すると、その列のデータをより効率的に検索できるようになります。たとえば、特定の範囲の LastName
値に対して SingerId
のセットをすばやく検索する必要がある場合は、Cloud Spanner でテーブル全体をスキャンしなくてもいいように、LastName
にセカンダリ インデックスを作成する必要があります。
上記の例で、読み取り / 書き込みトランザクション内で検索を行う場合、より効率的な検索では、テーブル全体をロックしたままにすることも回避します。これにより、LastName
の検索範囲外のテーブルの行に対する挿入や更新が可能になります。
Cloud Spanner では、各セカンダリ インデックスに次のデータが格納されます。
- ベーステーブルのすべてのキー列
- インデックスに含まれるすべての列
- インデックス定義のオプションの
STORING
句に指定されたすべての列
Cloud Spanner では、セカンダリ インデックスが適切なクエリに使用されるように、テーブルを経時的に分析します。
セカンダリ インデックスの追加
セカンダリ インデックスを最も効率的に追加できるタイミングは、テーブルの作成時です。テーブルとそのインデックスを同時に作成するには、新しいテーブルと新しいインデックスの DDL 文を 1 つのリクエストで Cloud Spanner に送信します。
Cloud Spanner では、データベースでトラフィックを引き続き処理しながら、既存のテーブルに新しいセカンダリ インデックスを追加することもできます。Cloud Spanner での他のスキーマの更新と同様に、既存のデータベースにインデックスを追加するときに、データベースをオフラインにする必要はありません。列またはテーブル全体をロックする必要もありません。
既存のテーブルに新しいインデックスが追加されると、Cloud Spanner はインデックスを自動的にバックフィルまたは入力し、インデックスに登録されているデータの最新状態を反映します。このバックフィル プロセスは Cloud Spanner で管理され、インデックスのバックフィル中に追加のリソースが使用されます。
インデックスの作成には数分から数時間かかります。インデックスの作成はスキーマ更新であるため、他のスキーマ更新と同じパフォーマンス制約でバインドされます。セカンダリ インデックスを作成するために必要な時間は、次の要因によって決まります。
- データセットのサイズ
- インスタンスのノードの数
- インスタンスの負荷
セカンダリ インデックスの最初の部分として commit タイムスタンプ列を使用すると、ホットスポットが作成され、書き込みパフォーマンスが低下する可能性があるので注意してください。
スキーマでセカンダリ インデックスを定義するには、CREATE INDEX
文を使用します。次に例を示します。
データベースのすべての Singers
を姓名でインデックスに登録するには、次の文を使用します。
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)
データベースのすべての Songs
のインデックスを SongName
の値で作成するには、次の文を使用します。
CREATE INDEX SongsBySongName ON Songs(SongName)
特定の歌手の曲のみにインデックスを作成するには、INTERLEAVE IN
句を使用して、次のようにテーブル Singers
のインデックスをインターリーブします。
CREATE INDEX SongsBySingerSongName ON Songs(SingerId, SongName),
INTERLEAVE IN Singers
特定のアルバムの曲のみにインデックスを作成する:
CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName),
INTERLEAVE IN Albums
SongName
の降順でインデックスに登録するには、次の文を使用します。
CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC),
INTERLEAVE IN Albums
上記の DESC
アノテーションは SongName
にのみ適用されます。他のインデックス キーの降順でインデックスに登録するには、DESC
と SingerId DESC, AlbumId DESC
というアノテーションを付けます。
また、PRIMARY_KEY
は予約語であり、インデックスの名前としては使用できないことにご注意ください。これは、主キーの仕様を持つテーブルが作成される際に作成される疑似インデックスに与えられる名前です。
インターリーブされていないインデックスとインターリーブされたインデックスの選択の詳細とベスト プラクティスについては、インデックス オプションと値が単調に増加または減少する列へのインターリーブされたインデックスの使用をご覧ください。
インデックス作成のキャンセル
Cloud SDK を使用すると、インデックスの作成をキャンセルできます。Cloud Spanner データベースのスキーマ更新オペレーションのリストを取得するには、gcloud spanner operations list
コマンドを使用して --filter
オプションを含めます。
gcloud spanner operations list \
--instance=INSTANCE \
--database=DATABASE \
--filter="@TYPE:UpdateDatabaseDdlMetadata"
キャンセルするオペレーションの OPERATION_ID
を検索してから、gcloud spanner operations cancel
コマンドを使用してキャンセルします。
gcloud spanner operations cancel OPERATION_ID \
--instance=INSTANCE \
--database=DATABASE
既存のインデックスの表示
データベース内の既存のインデックスに関する情報を表示するには、Google Cloud Console または gcloud
コマンドライン ツールを使用できます。
Console
Cloud Console で、Cloud Spanner の [インスタンス] ページに移動します。
確認するインスタンスの名前をクリックします。
左側のペインで、表示するデータベースをクリックし、表示するテーブルをクリックします。
[インデックス] タブをクリックします。Cloud Console に、インデックスのリストが表示されます。
オプション: インデックスに含まれる列など、インデックスの詳細を取得するには、インデックスの名前をクリックします。
gcloud
gcloud spanner databases ddl describe
コマンドを使用します。
gcloud spanner databases ddl describe DATABASE \
--instance=INSTANCE
gcloud
ツールにより、データベースのテーブルとインデックスを作成するためのデータ定義言語(DDL)文が出力されます。CREATE
INDEX
文は、既存のインデックスを記述します。次に例を示します。
--- |-
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
FirstName STRING(1024),
LastName STRING(1024),
SingerInfo BYTES(MAX),
) PRIMARY KEY(SingerId)
---
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)
特定のインデックスを使用したクエリ
以降のセクションでは、SQL ステートメントでインデックスを指定する方法と Cloud Spanner の読み取りインターフェースを使用する方法について説明します。これらのセクションの例では、MarketingBudget
列を Albums
テーブルに追加し、AlbumsByAlbumTitle
というインデックスを作成したと想定しています。
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);
SQL ステートメントでのインデックスの指定
SQL を使用して Cloud Spanner テーブルに対してクエリを実行する場合、Cloud Spanner では、クエリを効率化する可能性のあるインデックスが自動的に使用されます。結果として、通常、SQL クエリにインデックスを指定する必要はありません。
ただし、Cloud Spanner がクエリのレイテンシを増加させるインデックスを選択することがあります。パフォーマンス低下のトラブルシューティングの手順に従い、クエリに別のインデックスを試すことが理にかなっていると確認した場合、クエリの一部としてインデックスを指定できます。
SQL ステートメントでインデックスを指定するには、FORCE_INDEX
を使用してインデックス ディレクティブを指定します。インデックス ディレクティブでは、次の構文を使用します。
FROM MyTable@{FORCE_INDEX=MyTableIndex}
インデックスを使用せずにベーステーブルをスキャンするよう Cloud Spanner に指示するために、インデックス ディレクティブも使用できます。
FROM MyTable@{FORCE_INDEX=_BASE_TABLE}
次の例は、インデックスを指定する SQL クエリを示しています。
SELECT AlbumId, AlbumTitle, MarketingBudget
FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
WHERE AlbumTitle >= "Aardvark" AND AlbumTitle < "Goo";
インデックス ディレクティブは、Cloud Spanner のクエリ プロセッサに対して、(インデックスには格納されていないが)クエリに必要な追加の列を読み取るよう、強制する場合があります。クエリ プロセッサは、インデックスとベーステーブルを結合して、これらの列を取得します。この余分な結合を回避するには、STORING
句を使用して、追加の列をインデックスに格納します。
たとえば上記の例で、MarketingBudget
列はインデックスに格納されませんが、SQL クエリではこの列が選択されます。結果として、Cloud Spanner では、ベーステーブルで MarketingBudget
列を検索してからインデックスのデータと結合し、クエリ結果を返す必要があります。
インデックス ディレクティブに次のいずれかの問題がある場合、Cloud Spanner でエラーが発生します。
- インデックスが存在しない。
- インデックスが別のベーステーブルに作成されている。
NULL_FILTERED
インデックスに必要なNULL
フィルタリング式がクエリにない。
次の例は、インデックス AlbumsByAlbumTitle
を使用して AlbumId
、AlbumTitle
、MarketingBudget
の値をフェッチするクエリを作成して実行する方法を示しています。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
読み取りインターフェースでのインデックスの指定
Cloud Spanner に対して読み取りインターフェースを使用し、Cloud Spanner でインデックスを使用する場合、インデックスを指定する必要があります。読み取りインターフェースでインデックスが自動的に選択されることはありません。
また、インデックスには、主キーの一部である列を除いて、クエリ結果に表示されるすべてのデータを含める必要があります。この制限は、読み取りインターフェースでインデックスとベーステーブル間の結合がサポートされていないために存在します。クエリ結果に他の列を含める必要がある場合は、いくつかのオプションがあります。
STORING
句を使用して、追加の列をインデックスに格納します。- 追加の列を含めずにクエリを実行してから、主キーを使用して、追加の列を読み取る別のクエリを送信します。
Cloud Spanner では、インデックスの値はインデックス キーの昇順で返されます。値を降順で取得するには、次の手順を行います。
インデックス キーに
DESC
というアノテーションを付けます。次に例を示します。CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle DESC);
DESC
アノテーションは、単一のインデックス キーに適用されます。インデックスに複数のキーが含まれ、クエリ結果をすべてのキーに基づいて降順で表示する必要がある場合は、各キーについてDESC
アノテーションを追加します。読み取りでキー範囲が指定されている場合は、キー範囲も降順であることを確認します。つまり、開始キーの値は終了キーの値より大きい必要があります。
次の例は、インデックス AlbumsByAlbumTitle
を使用して、AlbumId
と AlbumTitle
の値を取得する方法を示しています。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
STORING 句
必要に応じて、STORING
句を使用してインデックスに列のコピーを保存できます。このタイプのインデックスを使用すると、クエリと読み取り呼び出しを実行する際に、次のメリットがあります(追加ストレージを使用する費用がかります)。
- インデックスを使用し、
STORING
句に保存された列を選択する SQL クエリで、ベーステーブルへの余分な結合が不要になる。 - インデックスを使用する読み取り呼び出しで、
STORING
句に保存された列を読み取ることができる。
たとえば、MarketingBudget
列のコピーをインデックスに保存する AlbumsByAlbumTitle
の代替バージョンを作成したとします(STORING
句は太字になっています)。
CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);
古い AlbumsByAlbumTitle
インデックスを使用する場合、Cloud Spanner では、インデックスをベーステーブルと結合した後にベーステーブルからその列を取得する必要があります。新しい AlbumsByAlbumTitle2
インデックスを使用する場合、Cloud Spanner では、インデックスから直接列を読み取るため、より効率的です。
SQL の代わりに読み取りインターフェースを使用する場合、新しい AlbumsByAlbumTitle2
インデックスを使用すると、MarketingBudget
列を直接読み取ることもできます。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
NULL 値のインデックス登録
デフォルトでは、Cloud Spanner は NULL
値をインデックスに登録します。たとえば、テーブル Singers
のインデックス SingersByFirstLastName
の定義を思い出してください。
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);
FirstName
または LastName
のいずれか、あるいは両方が NULL
でも、Singers
のすべての行がインデックスに登録されます。
NULL
値がインデックスに登録されている場合、NULL
値を含むデータ全体に対して効率的な SQL クエリと読み取りを実行できます。たとえば、NULL
FirstName
を含むすべての Singers
を検索するには、次の SQL クエリ文を使用します。
SELECT s.SingerId, s.FirstName, s.LastName
FROM Singers@{FORCE_INDEX=SingersByFirstLastName} AS s
WHERE s.FirstName IS NULL;
NULL 値の並べ替え順
Cloud Spanner では、指定された型で NULL
を最小値として並べ替えを行います。昇順(ASC
)の列の場合、NULL
値が最初になります。降順(DESC
)の列の場合、NULL
値が最後になります。
NULL 値のインデックス登録の無効化
Null のインデックス登録を無効にするには、インデックスの定義に NULL_FILTERED
キーワードを追加します。NULL_FILTERED
インデックスは、大半の行に NULL
値が含まれるスパース列をインデックスに登録する場合に特に便利です。この場合、NULL_FILTERED
インデックスは NULL
を含む通常のインデックスよりもかなりサイズが小さくなり、効率的に維持できます。
NULL
値をインデックスに登録しない SingersByFirstLastName
の代替定義は次のとおりです。
CREATE NULL_FILTERED INDEX SingersByFirstLastNameNoNulls
ON Singers(FirstName, LastName);
NULL_FILTERED
キーワードは、すべてのインデックス キー列に適用されます。列単位で NULL
フィルタリングを指定することはできません。
インデックスを NULL_FILTERED
にすると、Cloud Spanner では、このインデックスを一部のクエリで使用できなくなります。たとえば、Cloud Spanner では、次のクエリに対してこのインデックスは使用されません。これは、LastName
が NULL
であるすべての Singers
行がこのインデックスで省略されるため、このインデックスを使用すると、クエリで正しい行を返すことができなくなるためです。
FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
WHERE FirstName = "John";
Cloud Spanner でインデックスを使用できるようにするには、インデックスから除外される行をクエリからも除外するようにクエリを書き直す必要があります。
SELECT FirstName, LastName
FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
WHERE FirstName = "John" AND LastName IS NOT NULL;
一意のインデックス
インデックスは、UNIQUE
として宣言できます。UNIQUE
インデックスを使用すると、インデックスに登録するデータに制約が追加され、指定したインデックス キーでの重複が禁止されます。この制約は、トランザクションの commit 時に Cloud Spanner が適用します。同じキーに複数のインデックス エントリが存在するトランザクションは commit に失敗します。
テーブルの先頭に UNIQUE
以外のデータが存在する場合、UNIQUE
インデックスを作成しようとすると失敗します。
UNIQUE NULL_FILTERED インデックスに関する注意事項
インデックスのキー部分の 1 つ以上が NULL の場合、UNIQUE NULL_FILTERED
インデックスでインデックス キーの一意性は維持されません。
たとえば、次のテーブルとインデックスを作成したとします。
CREATE TABLE ExampleTable (
Key1 INT64 NOT NULL,
Key2 INT64,
Key3 INT64,
Col1 INT64,
) PRIMARY KEY (Key1, Key2, Key3);
CREATE UNIQUE NULL_FILTERED INDEX ExampleIndex ON ExampleTable (Key1, Key2, Col1);
ExampleTable
の次の 2 行では、セカンダリ インデックス キー Key1
、Key2
、Col1
に同じ値が設定されています。
1, NULL, 1, 1
1, NULL, 2, 1
Key2
が NULL
でインデックスが NULL_FILTERED
のため、インデックス ExampleIndex
に行がありません。これらはインデックスに挿入されないため、(Key1, Key2,
Col1)
の一意性に違反していても、インデックスはこれらの値を拒否しません。
インデックスでタプル(Key1
、Key2
、Col1
)の値の一意性を適用するには、テーブル定義で Key2
に NOT NULL
というアノテーションを付けるか、NULL_FILTERED
を使用せずにインデックスを作成する必要があります。
インデックスを削除する
スキーマからセカンダリ インデックスを削除するには、DROP INDEX
文を使用します。
SingersByFirstLastName
という名前のインデックスを削除するには、次のようにします。
DROP INDEX SingersByFirstLastName;
次のステップ
- Cloud Spanner に関する SQL のベスト プラクティスについて学習します。
- Cloud Spanner のクエリ実行プランを理解します。
- SQL クエリで発生するパフォーマンス低下のトラブルシューティング方法を学習します。