Spanner データベースでは、Cloud Spanner により、テーブルの主キーごとに自動的にインデックスが作成されます。たとえば、Singers
の主キーは自動的にインデックスに登録されるため、操作は必要はありません。
他の列のセカンダリ インデックスを作成することもできます。セカンダリ インデックスを列に追加すると、その列のデータをより効率的に検索できるようになります。たとえば、アルバムをタイトル別にすばやく検索する必要がある場合は、Spanner がテーブル全体をスキャンしなくてもいいように、AlbumTitle
にセカンダリ インデックスを作成する必要があります。
上記の例で、読み取り / 書き込みトランザクション内で検索を行う場合、より効率的な検索では、テーブル全体をロックしたままにすることも回避します。これにより、AlbumTitle
の検索範囲外のテーブルの行に対する挿入や更新が可能になります。
セカンダリ インデックスは、ルックアップを使用することで得られるメリットに加えて、Spanner でより効率的にスキャンを実行し、全テーブル スキャンではなくインデックス スキャンを行うこともできます。
Spanner では、各セカンダリ インデックスに次のデータが格納されます。
- ベーステーブルのすべてのキー列
- インデックスに含まれるすべての列
- インデックス定義のオプションの
STORING
句(GoogleSQL 言語データベース)またはINCLUDE
句(PostgreSQL 言語データベース)で指定されたすべての列。
Spanner では、セカンダリ インデックスが適切なクエリに使用されるように、テーブルを経時的に分析します。
セカンダリ インデックスの追加
セカンダリ インデックスを最も効率的に追加できるタイミングは、テーブルの作成時です。テーブルとそのインデックスを同時に作成するには、新しいテーブルと新しいインデックスの DDL 文を 1 つのリクエストで Spanner に送信します。
Spanner では、データベースでトラフィックを引き続き処理しながら、既存のテーブルに新しいセカンダリ インデックスを追加することもできます。Spanner での他のスキーマの更新と同様に、既存のデータベースにインデックスを追加するときに、データベースをオフラインにする必要はありません。列またはテーブル全体をロックする必要もありません。
既存のテーブルに新しいインデックスが追加されると、Spanner はインデックスを自動的にバックフィルまたは入力し、インデックスに登録されているデータの最新状態を反映します。このバックフィル プロセスは 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
は予約語であり、インデックスの名前としては使用できないことにご注意ください。これは、主キーの仕様を持つテーブルが作成される際に作成される疑似インデックスに与えられる名前です。
インターリーブされていないインデックスとインターリーブされたインデックスの選択の詳細とベスト プラクティスについては、インデックス オプションと値が単調に増加または減少する列へのインターリーブされたインデックスの使用をご覧ください。
インデックスのバックフィルの進行状況を表示
インデックスのバックフィルの進捗状況を表示する手順
セカンダリ インデックスの追加にはスキーマの更新が必要になるため、インデックスのバックフィルのプロセスはスキーマ更新長時間実行オペレーションの一部です。インデックスのバックフィルの進行状況は、オペレーション ID を使用して確認できます。OPERATION_ID がない場合は、gcloud spanner operations list を利用して検索します。
gcloud spanner operations list --instance=INSTANCE --database=DATABASE
使用上の注意:
このコマンドによって返されるオペレーションのリストを制限するには、
--filter
フラグを指定します。たとえば、次のフィルタを使用して、スキーマ更新オペレーションを返します。--filter="@TYPE:UpdateDatabaseDdlMetadata"
フィルタ構文の詳細については、gcloud topic filters をご覧ください。データベース オペレーションのフィルタリングについては、ListDatabaseOperationsRequest の
filter
フィールドをご覧ください。
出力の例を以下に示します。
OPERATION_ID STATEMENTS DONE @TYPE _auto_op_123456 CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName) False UpdateDatabaseDdlMetadata CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName), INTERLEAVE IN Albums _auto_op_234567 True CreateDatabaseMetadata
1 つ以上のセカンダリ インデックスのバックフィル プロセスの進捗状況を追跡するには、gcloud spanner operations describe を使用します。
gcloud spanner operations describe _auto_op_123456 \ --instance=INSTANCE \ --database=DATABASE
2 つのインデックス バックフィル プロセスを含む、スキーマ更新長時間実行オペレーションの出力例を次に示します。
done: true metadata: '@type': type.googleapis.com/google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata commitTimestamps: - '2021-01-22T21:58:42.912540Z' database: projects/my-project/instances/test-instance/databases/example-db progress: - endTime: '2021-01-22T21:58:42.912540Z' progressPercent: 100 startTime: '2021-01-22T21:58:11.053996Z' - progressPercent: 67 startTime: '2021-01-22T21:58:11.053996Z' statements: - CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName) - CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName), INTERLEAVE IN Albums name: projects/my-project/instances/test-instance/databases/example-db/operations/_auto_op_123456 response: '@type': type.googleapis.com/google.protobuf.Empty
各インデックス バックフィル ステートメントの進行状況は、
progress
フィールドで確認できます。ステートメント配列内のステートメントごとに、進捗状況配列に対応するフィールドが存在します。進捗状況の配列の順序は、ステートメントの配列の順序に対応しています。利用可能になると、startTime
、progressPercent
、endTime
フィールドに値が入力されます。出力には、バックフィルの進行状況を示す推定時間は表示されません。
インデックスのバックフィルの進捗状況を表示する場合のシナリオ
インデックスのバックフィルの進行状況を確認する際に発生するシナリオはいくつかあります。インデックスのバックフィルを必要とするインデックス作成ステートメントは、スキーマ更新オペレーションの一部です。いくつかのステートメントをスキーマ更新オペレーションの構成要素にできます。
最初のシナリオが最も簡単で、インデックス作成ステートメントがスキーマ更新オペレーションの最初のステートメントである場合です。インデックス作成ステートメントは最初のステートメントであるため、実行の順序に基づいて最初に処理され、実行されます。すぐに、インデックス作成ステートメントの startTime
フィールドに、スキーマ更新オペレーションの開始時刻が入力されます。次に、インデックスのバックフィルの進行状況が 0% を超えると、インデックス作成ステートメントの progressPercent
フィールドに値が入力されます。最後に、ステートメントが commit されると、endTime
フィールドに値が入力されます。
2 番目のシナリオは、インデックス作成ステートメントがスキーマ更新オペレーションの最初のステートメントではない場合です。実行の順序によって、前のステートメントが commit されるまで、インデックス作成ステートメントに関連するフィールドは入力されません。前のシナリオと同様に、前のステートメントが commit されると、インデックス作成ステートメントの startTime
フィールドが最初に入力され、次に progressPercent
フィールドが入力されます。最後に、ステートメントの commit が終了すると endTime
フィールドに値が入力されます。
インデックスの作成のキャンセル
Google Cloud CLI を使用して、インデックスの作成をキャンセルできます。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 コンソール または Google Cloud CLI を使用できます。
Console
Google Cloud コンソールで、Spanner の [インスタンス] ページに移動します。
確認するインスタンスの名前をクリックします。
左側のペインで、表示するデータベースをクリックし、表示するテーブルをクリックします。
[インデックス] タブをクリックします。Google Cloud Console に、インデックスのリストが表示されます。
オプション: インデックスに含まれる列など、インデックスの詳細を取得するには、インデックスの名前をクリックします。
gcloud
gcloud spanner databases ddl describe
コマンドを使用します。
gcloud spanner databases ddl describe DATABASE \
--instance=INSTANCE
gcloud CLI により、データベースのテーブルとインデックスを作成するためのデータ定義言語(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 ステートメントでインデックスを指定する方法と Spanner の読み取りインターフェースを使用する方法について説明します。これらのセクションの例では、MarketingBudget
列を Albums
テーブルに追加し、AlbumsByAlbumTitle
というインデックスを作成したと想定しています。
GoogleSQL
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);
PostgreSQL
CREATE TABLE Albums (
SingerId BIGINT NOT NULL,
AlbumId BIGINT NOT NULL,
AlbumTitle VARCHAR,
MarketingBudget BIGINT,
PRIMARY KEY (SingerId, AlbumId)
) INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);
SQL ステートメントでのインデックスの指定
SQL を使用して Spanner テーブルに対してクエリを実行する場合、Spanner では、クエリを効率化する可能性のあるインデックスが自動的に使用されます。結果として、SQL クエリにインデックスを指定する必要はありません。しかし、ワークロードに重要なクエリについては、より安定したパフォーマンスのため、SQL ステートメントで FORCE_INDEX
ディレクティブを使用することを Google はおすすめします。
まれに、Spanner がクエリのレイテンシを増加させるインデックスを選択することがあります。パフォーマンス低下のトラブルシューティングの手順に従い、クエリに別のインデックスを試すことが理にかなっていると確認した場合、クエリの一部としてインデックスを指定できます。
SQL ステートメントでインデックスを指定するには、FORCE_INDEX
ヒントを使用してインデックス ディレクティブを指定します。インデックス ディレクティブでは、次の構文を使用します。
GoogleSQL
FROM MyTable@{FORCE_INDEX=MyTableIndex}
PostgreSQL
FROM MyTable /*@ FORCE_INDEX = MyTableIndex */
インデックスを使用せずにベーステーブルをスキャンするよう Spanner に指示するために、インデックス ディレクティブも使用できます。
GoogleSQL
FROM MyTable@{FORCE_INDEX=_BASE_TABLE}
PostgreSQL
FROM MyTable /*@ FORCE_INDEX = _BASE_TABLE */
次の例は、インデックスを指定する SQL クエリを示しています。
GoogleSQL
SELECT AlbumId, AlbumTitle, MarketingBudget
FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
WHERE AlbumTitle >= "Aardvark" AND AlbumTitle < "Goo";
PostgreSQL
SELECT AlbumId, AlbumTitle, MarketingBudget
FROM Albums /*@ FORCE_INDEX = AlbumsByAlbumTitle */
WHERE AlbumTitle >= 'Aardvark' AND AlbumTitle < 'Goo';
インデックス ディレクティブは、Spanner のクエリ プロセッサに対して、(インデックスには格納されていないが)クエリに必要な追加の列を読み取るよう、強制する場合があります。クエリ プロセッサは、インデックスとベーステーブルを結合して、これらの列を取得します。この余分な結合を回避するには、STORING
句(GoogleSQL 言語データベース)または INCLUDE
句(PostgreSQL 言語データベース)を使用して、追加の列をインデックスに保存します。
たとえば上記の例で、MarketingBudget
列はインデックスに格納されませんが、SQL クエリではこの列が選択されます。結果として、Spanner では、ベーステーブルで MarketingBudget
列を検索してからインデックスのデータと結合し、クエリ結果を返す必要があります。
インデックス ディレクティブに次のいずれかの問題がある場合、Spanner でエラーが発生します。
- インデックスが存在しない。
- インデックスが別のベーステーブルに作成されている。
NULL_FILTERED
インデックスに必要なNULL
フィルタリング式がクエリにない。
次の例は、インデックス AlbumsByAlbumTitle
を使用して AlbumId
、AlbumTitle
、MarketingBudget
の値をフェッチするクエリを作成して実行する方法を示しています。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
読み取りインターフェースでのインデックスの指定
Spanner に対して読み取りインターフェースを使用し、Spanner でインデックスを使用する場合、インデックスを指定する必要があります。読み取りインターフェースでインデックスが自動的に選択されることはありません。
また、インデックスには、主キーの一部である列を除いて、クエリ結果に表示されるすべてのデータを含める必要があります。この制限は、読み取りインターフェースでインデックスとベーステーブル間の結合がサポートされていないために存在します。クエリ結果に他の列を含める必要がある場合は、いくつかのオプションがあります。
STORING
句INCLUDE
を使用して、追加の列をインデックスに格納します。- 追加の列を含めずにクエリを実行してから、主キーを使用して、追加の列を読み取る別のクエリを送信します。
Spanner では、インデックスの値はインデックス キーの昇順で返されます。値を降順で取得するには、次の手順を行います。
インデックス キーに
DESC
というアノテーションを付けます。次に例を示します。CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle DESC);
DESC
アノテーションは、単一のインデックス キーに適用されます。インデックスに複数のキーが含まれ、クエリ結果をすべてのキーに基づいて降順で表示する必要がある場合は、各キーについてDESC
アノテーションを追加します。読み取りでキー範囲が指定されている場合は、キー範囲も降順であることを確認します。つまり、開始キーの値は終了キーの値より大きい必要があります。
次の例は、インデックス AlbumsByAlbumTitle
を使用して、AlbumId
と AlbumTitle
の値を取得する方法を示しています。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
インデックスのみのスキャン用のインデックスを作成する
必要に応じて、STORING
句(GoogleSQL 言語データベースの場合)または INCLUDE
句(PostgreSQL 言語データベースの場合)を使用してインデックスに列のコピーを保存できます。このタイプのインデックスを使用すると、クエリと読み取り呼び出しを実行する際に、次のメリットがあります(追加ストレージを使用する費用がかります)。
- インデックスを使用し、
STORING
またはINCLUDE
句に保存された列を選択する SQL クエリで、ベーステーブルへの余分な結合が不要になります。 - インデックスを使用する
read()
呼び出しは、STORING
/INCLUDE
句に保存された列を読み取ることができます。
たとえば、MarketingBudget
列のコピーをインデックスに保存する AlbumsByAlbumTitle
の代替バージョンを作成したとします(STORING
または INCLUDE
句は太字になっています)。
GoogleSQL
CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);
PostgreSQL
CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget);
古い AlbumsByAlbumTitle
インデックスを使用する場合、Spanner では、インデックスをベーステーブルと結合した後にベーステーブルからその列を取得する必要があります。新しい AlbumsByAlbumTitle2
インデックスを使用する場合、Spanner では、インデックスから直接列を読み取るため、より効率的です。
SQL の代わりに読み取りインターフェースを使用する場合、新しい AlbumsByAlbumTitle2
インデックスを使用すると、MarketingBudget
列を直接読み取ることもできます。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
インデックスの変更
ALTER INDEX
ステートメントを使用して、既存のインデックスまたはドロップ列に列を追加できます。インデックスを作成する場合は、STORING
句(GoogleSQL 言語データベース)または INCLUDE
句(PostgreSQL 言語データベース)によって定義された列リストを更新できます。このステートメントを使用して、インデックス キーに対して列の追加または削除を行うことはできません。たとえば、新しいインデックス AlbumsByAlbumTitle2
を作成する代わりに、次の例に示すように ALTER INDEX
を使用して AlbumsByAlbumTitle
に列を追加できます。
GoogleSQL
ALTER INDEX AlbumsByAlbumTitle ADD STORED COLUMN MarketingBudget
PostgreSQL
ALTER INDEX AlbumsByAlbumTitle ADD INCLUDE COLUMN MarketingBudget
既存のインデックスに新しい列を追加すると、Cloud Spanner はバックグラウンド バックフィル プロセスを使用します。バックフィルの進行中は、インデックス内の列が読み取れないため、期待されるパフォーマンスの向上が得られない場合があります。gcloud spanner operations
コマンドを使用すると、長時間実行オペレーションを一覧表示して、そのステータスを確認できます。詳細については、オペレーションの説明をご覧ください。
キャンセル オペレーションを使用して、実行中のオペレーションをキャンセルすることもできます。
バックフィルが完了すると、Cloud Spanner によってインデックスに列が追加されます。インデックスのサイズが大きくなると、インデックスを使用するクエリの速度が低下する可能性があります。
次の例は、インデックスから列を削除する方法を示しています。
GoogleSQL
ALTER INDEX AlbumsByAlbumTitle DROP STORED COLUMN MarketingBudget
PostgreSQL
ALTER INDEX AlbumsByAlbumTitle DROP INCLUDE COLUMN MarketingBudget
NULL 値のインデックス
デフォルトでは、Spanner は NULL
値をインデックスに登録します。たとえば、テーブル Singers
のインデックス SingersByFirstLastName
の定義を思い出してください。
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);
FirstName
または LastName
のいずれか、あるいは両方が NULL
でも、Singers
のすべての行がインデックスに登録されます。
NULL
値がインデックスに登録されている場合、NULL
値を含むデータ全体に対して効率的な SQL クエリと読み取りを実行できます。たとえば、NULL
FirstName
を含むすべての Singers
を検索するには、次の SQL クエリ文を使用します。
GoogleSQL
SELECT s.SingerId, s.FirstName, s.LastName
FROM Singers@{FORCE_INDEX=SingersByFirstLastName} AS s
WHERE s.FirstName IS NULL;
PostgreSQL
SELECT s.SingerId, s.FirstName, s.LastName
FROM Singers /* @ FORCE_INDEX = SingersByFirstLastName */ AS s
WHERE s.FirstName IS NULL;
NULL 値の並べ替え順
Spanner では、指定された型で NULL
を最小値として並べ替えを行います。昇順(ASC
)の列の場合、NULL
値が最初になります。降順(DESC
)の列の場合、NULL
値が最後になります。
NULL 値のインデックス登録を無効にする
GoogleSQL
Null のインデックス登録を無効にするには、インデックスの定義に NULL_FILTERED
キーワードを追加します。NULL_FILTERED
インデックスは、大半の行に NULL
値が含まれるスパース列をインデックスに登録する場合に特に便利です。この場合、NULL_FILTERED
インデックスは NULL
を含む通常のインデックスよりもかなりサイズが小さくなり、効率的に維持できます。
NULL
値をインデックスに登録しない SingersByFirstLastName
の代替定義は次のとおりです。
CREATE NULL_FILTERED INDEX SingersByFirstLastNameNoNulls
ON Singers(FirstName, LastName);
NULL_FILTERED
キーワードは、すべてのインデックス キー列に適用されます。列単位で NULL
フィルタリングを指定することはできません。
PostgreSQL
1 つ以上のインデックス列で null 値を持つ行を除外するには、WHERE COLUMN IS NOT NULL
述語を使用します。Null フィルタ付きインデックスは、大半の行に NULL
値が含まれるスパース列をインデックスに登録する場合に特に便利です。この場合、NULL
インデックスは を含む通常のインデックスよりもかなりサイズが小さくなり、効率的に維持できます。
NULL
値をインデックスに登録しない SingersByFirstLastName
の代替定義は次のとおりです。
CREATE INDEX SingersByFirstLastNameNoNulls
ON Singers(FirstName, LastName)
WHERE FirstName IS NOT NULL
AND LastName IS NOT NULL;
NULL
値を除外すると、Spanner は一部のクエリでその値を使用できません。たとえば、Spanner では、次のクエリに対してこのインデックスは使用されません。これは、LastName
が NULL
であるすべての Singers
行がこのインデックスで省略されるため、このインデックスを使用すると、クエリで正しい行を返すことができなくなるためです。
GoogleSQL
FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
WHERE FirstName = "John";
PostgreSQL
FROM Singers /*@ FORCE_INDEX = SingersByFirstLastNameNoNulls */
WHERE FirstName = 'John';
Spanner でインデックスを使用できるようにするには、インデックスから除外される行をクエリからも除外するようにクエリを書き直す必要があります。
GoogleSQL
SELECT FirstName, LastName
FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
WHERE FirstName = 'John' AND LastName IS NOT NULL;
PostgreSQL
SELECT FirstName, LastName
FROM Singers /*@ FORCE_INDEX = SingersByFirstLastNameNoNulls */
WHERE FirstName = 'John' AND LastName IS NOT NULL;
一意のインデックス
インデックスは、UNIQUE
として宣言できます。UNIQUE
インデックスを使用すると、インデックスに登録するデータに制約が追加され、指定したインデックス キーでの重複が禁止されます。この制約は、トランザクションの commit 時に Spanner が適用します。同じキーに複数のインデックス エントリが存在するトランザクションは commit に失敗します。
テーブルの先頭に UNIQUE
以外のデータが存在する場合、UNIQUE
インデックスを作成しようとすると失敗します。
UNIQUE NULL_FILTERED インデックスに関する注意事項
インデックスのキー部分の 1 つ以上が NULL の場合、UNIQUE NULL_FILTERED
インデックスでインデックス キーの一意性は維持されません。
たとえば、次のテーブルとインデックスを作成したとします。
GoogleSQL
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);
PostgreSQL
CREATE TABLE ExampleTable (
Key1 BIGINT NOT NULL,
Key2 BIGINT,
Key3 BIGINT,
Col1 BIGINT,
PRIMARY KEY (Key1, Key2, Key3)
);
CREATE UNIQUE INDEX ExampleIndex ON ExampleTable (Key1, Key2, Col1)
WHERE Key1 IS NOT NULL
AND Key2 IS NOT NULL
AND Col1 IS NOT NULL;
ExampleTable
の次の 2 行では、セカンダリ インデックス キー Key1
、Key2
、Col1
に同じ値が設定されています。
1, NULL, 1, 1
1, NULL, 2, 1
Key2
が NULL
でインデックスが null でフィルタリングされているため、インデックス ExampleIndex
に行がありません。 これらはインデックスに挿入されないため、(Key1, Key2,
Col1)
の一意性に違反していても、インデックスはこれらの値を拒否しません。
インデックスでタプル(Key1
、Key2
、Col1
)の値の一意性を適用するには、テーブル定義で Key2
に NOT NULL
というアノテーションを付けるか、 を使用せずにインデックスを作成する必要があります。
インデックスを削除する
スキーマからセカンダリ インデックスを削除するには、DROP INDEX
文を使用します。
SingersByFirstLastName
という名前のインデックスを削除するには、次のようにします。
DROP INDEX SingersByFirstLastName;
迅速なスキャンのためのインデックス
Spanner で 1 つ以上の列から値をフェッチするために、(インデックス付きルックアップではなく)テーブル スキャンを実行する必要がある場合、その列にインデックスがあり、クエリで指定された順序になっていれば、よりすばやく結果を得られます。スキャンが必要なクエリを頻繁に実行する場合は、セカンダリ インデックスを作成することをおすすめします。これは、スキャンを効率的に実行するのに役立ちます。
特に、テーブルの主キーやその他のインデックスを逆順で頻繁に Spanner がスキャンする必要がある場合は、目的の順序を明確にするセカンダリ インデックスを使用することで、効率を高めることができます。
たとえば、次のクエリでは、Spanner が SongId
の最小値を見つけるために Songs
をスキャンする必要がありますが、常にすばやく結果が返されます。
SELECT SongId FROM Songs LIMIT 1;
SongId
は、テーブルの主キーであり、すべての主キーと同様に昇順で保存されます。Spanner では、そのキーのインデックスをスキャンして最初の結果をすばやく取得できます。
ただし、特に Songs
が大量のデータを保持している場合には、セカンダリ インデックスを使用しないと、次のクエリはすばやく結果を返しません。
SELECT SongId FROM Songs ORDER BY SongId DESC LIMIT 1;
SongId
はテーブルの主キーですが、Spanner では、テーブル全体をスキャンしないと、列の最大値をフェッチすることができません。
次のインデックスを追加すると、このクエリの結果がすばやく返されるようになります。
CREATE INDEX SongIdDesc On Songs(SongId DESC);
適宜このインデックスを使用すると、Spanner は 2 番目のクエリの結果をより迅速に返します。
次のステップ
- Spanner に関する SQL のベスト プラクティスについて学習します。
- Spanner のクエリ実行プランを理解します。
- SQL クエリで発生するパフォーマンス低下のトラブルシューティング方法を学習します。