データ操作言語のベスト プラクティス

このページでは、データ操作言語(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 にインデックスがない場合は、テーブル全体をスキャンしてターゲットのシンガーを見つける必要があります。更新の効率を高めるためにセカンダリ インデックスを追加しない場合は、SingerId 列を WHERE 句に含めます。

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 では、バッチ内のステートメントのグループに最適化を適用して、データ更新をより迅速かつ効率的に行うことができます。

  • 単一のリクエストで書き込みを実行する

    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 ステートメントのセットにパフォーマンス上のメリットがもたらされます。

    たとえば、サンプル スキーマには、テーブル SingersAlbumsAccounts があります。AlbumsSingers 内にインターリーブされ、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 セマンティクスが維持されます。