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

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

データ操作言語(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 ステートメントの後に制約がチェックされます。ミューテーションを使用した場合、Spanner は commit するまでクライアントのバッファにミューテーションを保存し、commit 時に制約のチェックを行います。各 DML ステートメントの後で制約を評価することで、Spanner は、同じトランザクション内の後続のクエリによって返されたデータがスキーマと整合するデータを返すことを保証します。

  • 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 するまで後続のステートメントで参照できないためです。トランザクションの最後にのみ書き込みをバッファリングするのが理想的です。

次のステップ