DML とミューテーションを比較する

Spanner には、データを変更するために使える API として、データ操作言語(DML)とミューテーションの 2 つがあります。どちらのツールにも同様のデータ操作機能があります。このページでは、両方のアプローチを比較します。

データ操作言語(DML)とは

Spanner のデータ操作言語(DML)を使用すると、INSERTUPDATEDELETE の各ステートメントを使用してデータベース テーブル内のデータを操作できます。DML ステートメントを実行するには、クライアント ライブラリGoogle Cloud コンソールgcloud spanner を使用します。

Spanner では DML 実行に関して、それぞれ異なる特性を持つ以下の 2 種類の実装が提供されています。

  • 標準 DML - 標準的なオンライン トランザクション処理(OLTP)ワークロードに適しています。

    コードサンプルを含む詳細については、DML の使用に関する説明をご覧ください。

  • パーティション化 DML - 以下の例のように、一括更新と削除用に設計されています。

    • 定期的なクリーンアップとガベージ コレクション。たとえば、古い行を削除したり、列を NULL に設定したりします。

    • デフォルト値での新しい列のバックフィリング。たとえば、UPDATE ステートメントを使用して、現在 NULL になっている列の新しい値に False を設定します。

    コードサンプルを含む詳細については、パーティション化 DML の使用をご覧ください

    読み取りオペレーションを伴わず、アトミック トランザクションを必要としない大量の書き込みオペレーションには、バッチ書き込みを使用できます。詳細については、バッチ書き込みを使用してデータを変更するをご覧ください。

ミューテーションとは

ミューテーションは、Spanner がデータベース内のさまざまな行やテーブルに対してアトミックに挿入、更新、削除を適用する一連の操作を表しています。異なる行または異なるテーブルに適用されるオペレーションをミューテーションに含めることができます。1 つ以上の書き込みを含むミューテーションを 1 つ以上定義したら、そのミューテーションを書き込みを commit するために適用します各変更は、ミューテーションに追加された順序で適用されます

コードサンプルを含む詳細については、ミューテーションを使用したデータの挿入、更新、削除をご覧ください。

DML とミューテーションの機能の比較

次の表に、DML とミューテーションの一般的なデータベース オペレーションと機能に関するサポートをまとめます。

運用 DML ミューテーション
データの挿入 サポート対象 サポート対象
データの削除 サポート対象 サポート対象
データの更新 サポート対象 サポート対象
データの挿入または無視 サポート対象 サポート対象外
書き込み後読み取り(RYW) サポート対象 サポート対象外
データの挿入または更新(Upsert) サポート対象 サポート対象
SQL 構文 サポート対象 サポート対象外
制約チェック 各ステートメントの後 commit 時

DML とミューテーションは、次の機能のサポートに違いがあります。

  • 書き込み後読み取り: 有効なトランザクション内で commit されていない結果を読み取ります。DML ステートメントで行った変更は、同じトランザクション内の後続のステートメントで参照できます。これはミューテーションの使用とは異なります。ミューテーションでは、トランザクションが commit されるまで、すべての読み取り(同じトランザクションで行われた読み取りを含む)では変更を参照できません。トランザクション内のミューテーションはクライアント側で(ローカルで)バッファリングされており、commit オペレーションでサーバーに送信されるためです。このため、commit リクエストのミューテーションは、同じトランザクション内の SQL ステートメントまたは DML ステートメントで参照できません。

  • 制約チェック: Spanner では、すべての DML ステートメントの後に制約がチェックされます。ミューテーションを使用した場合、commit するまでクライアントのバッファにミューテーションが保存され、commit 時に制約のチェックが行われます。各 DML ステートメントの後で制約を評価することで、同じトランザクション内の後続のクエリによって返されたデータがスキーマと整合するデータを返すことが保証されます。

  • SQL 構文: DML は、従来の方法でデータを操作できます。SQL スキルを再利用することで、DML API を使用したデータの変更が可能です。

ベスト プラクティス - 同じトランザクションで DML とミューテーションを混在させない

commit リクエストでトランザクションに DML ステートメントとミューテーションの両方が含まれている場合、Spanner はミューテーションの前に DML ステートメントを実行します。クライアント ライブラリ コードの実行順序を考慮する必要がないようにするには、1 つのトランザクション内に DML ステートメントまたはミューテーションのいずれかを使用します。両方を使用する必要はありません。

次の Java の例は、予期せぬ動作を示しています。このコードは、Mutation API を使用して 2 つの行をアルバムに挿入します。次に、スニペットは executeUpdate() を呼び出して、新しく挿入された行を更新し、executeQuery() を呼び出して更新されたアルバムを読み取ります。

static void updateMarketingBudget(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(
          new TransactionCallable<Void>() {
            @Override
            public Void run(TransactionContext transaction) throws Exception {
               transaction.buffer(
                    Mutation.newInsertBuilder("Albums")
                        .set("SingerId")
                        .to(1)
                        .set("AlbumId")
                        .to(1)
                        .set("AlbumTitle")
                        .to("Total Junk")
                        .set("MarketingBudget")
                        .to(800)
                        .build());
               transaction.buffer(
                    Mutation.newInsertBuilder("Albums")
                        .set("SingerId")
                        .to(1)
                        .set("AlbumId")
                        .to(2)
                        .set("AlbumTitle")
                        .to("Go Go Go")
                        .set("MarketingBudget")
                        .to(200)
                        .build());

                // This UPDATE will not include the Albums inserted above.
                String sql =
                  "UPDATE Albums SET MarketingBudget = MarketingBudget * 2"
                      + " WHERE SingerId = 1";
                long rowCount = transaction.executeUpdate(Statement.of(sql));
                System.out.printf("%d records updated.\n", rowCount);

                // Read a newly updated record.
                sql =
                  "SELECT SingerId, AlbumId, AlbumTitle FROM Albums"
                      + " WHERE SingerId = 1 AND MarketingBudget < 1000";
                ResultSet resultSet =
                                 transaction.executeQuery(Statement.of(sql));
                while (resultSet.next()) {
                   System.out.printf(
                        "%s %s\n",
                        resultSet.getString("FirstName"),
                        resultSet.getString("LastName"));
                }
                return null;
              }
            });
}

このコードを実行した場合、0 レコードが更新された旨が表示されます。その理由は、Mutation を使用して行った変更が、トランザクションを commit するまで後続のステートメントで参照できないためです。トランザクションの最後にのみ書き込みをバッファリングするのが理想的です。

次のステップ