このページでは、データ操作言語(DML)とパーティション化 DML を使用するベスト プラクティスについて説明します。
WHERE 句を使用してロックのスコープを狭める
DML ステートメントを読み取り / 書き込みトランザクション内で実行します。Spanner がデータを読み取るとき、読み取る行範囲の一部に対する共有読み取りロックを取得します。具体的には、アクセスする列に対してのみロックを取得します。ロックには、WHERE
句のフィルタ条件と一致しないデータを含めることが可能です。
Spanner が DML ステートメントを使用してデータを変更するとき、変更する特定のデータに対して排他ロックを取得します。また、データの読み取り時と同様に共有ロックを取得します。リクエストに大きな行範囲やテーブル全体が含まれている場合、共有ロックによって他のトランザクションが並行して進められない可能性があります。
データをできるだけ効率的に変更するには、Spanner で必要な行のみを読み取る WHERE
句を使用します。主キーまたはセカンダリ インデックスのキーに対するフィルタを使用して、この目標を達成できます。WHERE
句は共有ロックのスコープを制限し、Spanner で更新をより効率的に処理できるようにします。
たとえば、Singers
テーブルに含まれているあるミュージシャンが名前を変更した場合、データベース内のその名前を更新する必要があります。次の DML ステートメントを実行できますが、Spanner がテーブル全体を強制的にスキャンし、テーブル全体を対象とする共有ロックを取得します。そのため、Spanner は必要以上に多くのデータを読み取る必要があり、並行トランザクションでデータを並行して変更できません。
-- ANTI-PATTERN: SENDING AN UPDATE WITHOUT THE PRIMARY KEY COLUMN
-- IN THE WHERE CLAUSE
UPDATE Singers SET FirstName = "Marcel"
WHERE FirstName = "Marc" AND LastName = "Richards";
更新を効率化するには、SingerId
列を WHERE
句に含めます。SingerId
列は、Singers
テーブルの唯一の主キー列です。
-- ANTI-PATTERN: SENDING AN UPDATE THAT MUST SCAN THE ENTIRE TABLE
UPDATE Singers SET FirstName = "Marcel"
WHERE FirstName = "Marc" AND LastName = "Richards"
FirstName
または LastName
にインデックスがない場合は、テーブル全体をスキャンしてターゲットのシンガーを見つける必要があります。更新を効率化するためにセカンダリ インデックスを追加しない場合は、WHERE
句に SingerId
列を含めます。
SingerId
列は、Singers
テーブルの唯一の主キー列です。これを確認するには、更新トランザクションの前に、別の読み取り専用トランザクションで SELECT
を実行します。
SELECT SingerId
FROM Singers
WHERE FirstName = "Marc" AND LastName = "Richards"
-- Recommended: Including a seekable filter in the where clause
UPDATE Singers SET FirstName = "Marcel"
WHERE SingerId = 1;
同じトランザクション内で DML ステートメントとミューテーションを使用しない
Spanner は、サーバー側で DML ステートメントを使用して実行された挿入、更新、削除をバッファに保存します。この結果は、同じトランザクション内の後続の SQL ステートメントと DML ステートメントで参照できます。この動作は Mutation API とは異なります。Spanner は、クライアント側でミューテーションをバッファに保存し、commit オペレーションでサーバー側にミューテーションを送信します。このため、commit リクエストのミューテーションは、同じトランザクション内の SQL ステートメントまたは DML ステートメントで参照できません。
同じトランザクションで DML ステートメントとミューテーションの両方を使用しないでください。同じトランザクションで両方を使用する場合は、クライアント ライブラリ コードで実行順序を考慮する必要があります。同じリクエストでトランザクションに DML ステートメントとミューテーションの両方が含まれている場合、Spanner はミューテーションの前に DML ステートメントを実行します。
ミューテーションを使用してのみサポートされるオペレーションの場合は、同じトランザクションで DML ステートメントとミューテーションを組み合わせる必要があります(insert_or_update
など)。
両方を使用すると、バッファへの書き込みはトランザクションの最後でのみ行われます。
commit タイムスタンプを書き込むには PENDING_COMMIT_TIMESTAMP 関数を使用する
GoogleSQL
DML ステートメントで commit タイムスタンプを作成するには、PENDING_COMMIT_TIMESTAMP
関数を使用します。トランザクションが commit されると、Spanner が commit タイムスタンプを選択します。
PostgreSQL
DML ステートメントで commit タイムスタンプを作成するには、SPANNER.PENDING_COMMIT_TIMESTAMP()
関数を使用します。トランザクションが commit されると、Spanner が commit タイムスタンプを選択します。
パーティション化 DML と日付 / タイムスタンプ関数
パーティション化 DML は 1 つ以上のトランザクションを使用します。これらのトランザクションは異なる時間に実行され、commit される可能性があります。日付関数またはタイムスタンプ関数を使用すると、変更した行に異なる値が格納される可能性があります。
バッチ DML を使用してレイテンシを改善する
レイテンシを短縮するには、バッチ DML を使用して、単一のクライアント サーバー ラウンド トリップ内で複数の DML ステートメントを Spanner に送信します。
バッチ DML では、バッチ内のステートメントのグループに最適化を適用して、データ更新をより迅速かつ効率的に行うことができます。
1 つのリクエストで書き込みを実行する
Spanner は、データ依存関係に違反しない限り、パラメータ値が異なる類似の INSERT、UPDATE、DELETE バッチ ステートメントの連続するグループを自動的に最適化します。
たとえば、多数の新しい行のセットを
Albums
というテーブルに挿入するシナリオを考えてみます。Spanner が必要なすべてのINSERT
ステートメントを単一の効率的なサーバー側アクションに最適化できるようにするには、まず SQL クエリ パラメータを使用する適切な DML ステートメントを作成します。INSERT INTO Albums (SingerId, AlbumId, AlbumTitle) VALUES (@Singer, @Album, @Title);
次に、このステートメントを繰り返し連続して呼び出す DML バッチを送信します。繰り返しは、ステートメントの 3 つのクエリ パラメータにバインドする値でのみ異なります。Spanner は、これらの構造的に同じ DML ステートメントを、実行前に単一のサーバー側オペレーションに最適化します。
書き込みを並行して実行する
Spanner は、データ依存関係に違反しなければ、DML ステートメントの連続するグループを自動的に並行して最適化します。この最適化により、DML ステートメントの種類(INSERT、UPDATE、DELETE)の組み合わせと、パラメータ化された DML ステートメントとパラメータ化されていない DML ステートメントの両方に適用できるため、バッチ処理される DML ステートメントのセットにパフォーマンス上のメリットがもたらされます。
たとえば、サンプル スキーマには
Singers
、Albums
、Accounts
のテーブルがあります。Albums
はSingers
内にインターリーブされ、Singers
のアルバムに関する情報を格納します。次の連続するステートメント グループは、複数のテーブルに新しい行を書き込み、複雑なデータ依存関係はありません。INSERT INTO Singers (SingerId, Name) VALUES(1, "John Doe"); INSERT INTO Singers (SingerId, Name) VALUES(2, "Marcel Richards"); INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (1, 10001, "Album 1"); INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (1, 10002, "Album 2"); INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (2, 10001, "Album 1"); UPDATE Accounts SET Balance = 100 WHERE AccountId = @AccountId;
Spanner は、ステートメントを並行して実行することで、この DML ステートメントのグループを最適化します。バッチ内のステートメントの順番で書き込みが適用され、実行中にステートメントが失敗した場合でもバッチ DML セマンティクスが維持されます。