パーティション化されたデータ操作言語

パーティション化されたデータ操作言語(パーティション化 DML)は、次のような一括更新や削除用に設計されています。

  • 定期的なクリーンアップとガベージ コレクション。たとえば、古い行を削除したり、列を NULL に設定したりします。
  • デフォルト値での新しい列のバックフィリング。たとえば、UPDATE ステートメントを使用して、現在 NULL になっている列の新しい値に False を設定します。

DML とパーティション化 DML

Cloud Spanner は、DML ステートメントの 2 つの実行モードをサポートしています。

  • DML はトランザクション処理に適しています。 詳しくは、DML の使用をご覧ください。

  • パーティション化 DML は、キー空間を分割し、スコープの小さい個別のトランザクションでパーティションにステートメントを実行します。これにより、同時トランザクション処理に及ぼす影響を最小限に抑えながら、データベース全体にわたる大規模なオペレーションを実行できます。 詳細は、パーティション化 DML の使用をご覧ください。

次の表に、2 つの実行モードの主な違いを示します。

DML パーティション化 DML
WHERE 句と一致しない行はロックされる可能性があります。 WHERE 句と一致する行のみがロックされます。
トランザクション サイズに上限があります。 Cloud Spanner がトランザクション数の上限とトランザクションごとの同時実行の制限を処理します。
ステートメントをべき等にする必要はありません。 整合性のある結果を保証するため、DML ステートメントをべき等にする必要があります。
トランザクションに複数の DML ステートメントと SQL 文を含めることができます。 パーティション化されたトランザクションには、1 つの DML ステートメントのみを含めることができます。
ステートメントの複雑さに制限はありません。 ステートメントは完全に分割可能でなければなりません。
クライアント コードに読み取り / 書き込みトランザクションを作成します。 Cloud Spanner がトランザクションを作成します。

分割可能とべき等

パーティション化 DML ステートメントを実行する場合、1 つのパーティションの行から別のパーティションの行にアクセスすることはできません。Cloud Spanner によるパーティションの作成方法を制御することはできません。パーティショニングによりスケーラビリティが維持されますが、パーティション化 DML ステートメントは完全に分割可能でなければなりません。パーティション化 DML ステートメントは、一連のステートメントの結合として表現する必要があります。各ステートメントはテーブルの 1 つの行にのみアクセスし、他のテーブルにはアクセスできないように記述する必要があります。たとえば、複数のテーブルにアクセスしたり、自己結合を実行したりする DML ステートメントはパーティション化できません。DML ステートメントがパーティション化できない場合、Cloud Spanner は BadUsage エラーを返します。

次の DML ステートメントは、各ステートメントがテーブルの単一行に適用できるため、完全なパーティション化が可能な状態です。

UPDATE Singers SET Available = TRUE WHERE Available IS NULL

DELETE FROM Concerts
WHERE DATE_DIFF(CURRENT_DATE(), ConcertDate, DAY) > 365

次の DML ステートメントは、複数のテーブルにアクセスするため、完全なパーティション化はできません。

# Not fully partitionable
DELETE FROM Singers WHERE
SingerId NOT IN (SELECT SingerId FROM Concerts);

ネットワーク レベルの再試行が原因で、Cloud Spanner が一部のパーティションにパーティション化 DML ステートメントを複数回実行することがあります。結果として、1 つの行に対してステートメントが複数回実行される可能性があります。したがって、整合性のある結果を維持するため、ステートメントをべき等にする必要があります。1 つの行にステートメントを複数回実行しても同じ結果になる場合、ステートメントはべき等です。

次の DML ステートメントはべき等です。

UPDATE Singers SET MarketingBudget = 1000 WHERE true

次の DML ステートメントはべき等ではありません。

UPDATE Singers SET MarketingBudget = 1.5 * MarketingBudget WHERE true

ロック

行が更新または削除の対象である場合にのみ、Cloud Spanner はロックを取得します。この動作は DML の実行と異なります。DML の場合、WHERE 句と一致しない行に読み取りロックを取得することがあります。

実行とトランザクション

DML ステートメントのパーティション化は、実行に選択したクライアント ライブラリのメソッドによって異なります。各クライアント ライブラリには、DML の実行用とパーティション化 DML の実行用のメソッドが用意されています。

クライアント ライブラリのメソッドの呼び出しでは、パーティション化 DML ステートメントを 1 つのみ実行できます。

Cloud Spanner は、テーブル全体に対してパーティション化 DML ステートメントをアトミックに適用しません。ただし、Cloud Spanner は各パーティションに対してパーティション化 DML ステートメントをアトミックに適用します。

パーティション化 DML は commit またはロールバックをサポートしていません。Cloud Spanner は、DML ステートメントをすぐに実行し、適用します。

  • オペレーションをキャンセルした場合、Cloud Spanner は実行中のパーティションをキャンセルし、残りのパーティションを開始しません。Cloud Spanner は、すでに実行されているパーティションをロールバックしません。
  • ステートメントの実行でエラーが発生すると、すべてのパーティションで実行が停止し、Cloud Spanner がオペレーション全体のエラーを返します。たとえば、データ型制約の違反、UNIQUE INDEX の違反、ON DELETE NO ACTION の違反などのエラーを返します。実行が失敗した時点によっては、一部のパーティションでステートメントが正常に実行され、他のパーティションではパーティションが実行されていない可能性があります。

パーティション化 DML ステートメントが成功した場合、Cloud Spanner はキー範囲の各パーティションに対して少なくとも 1 回ステートメントを実行しています。

変更された行の数

パーティション化 DML ステートメントは、変更された行数の下限を返します。Cloud Spanner が変更された行をすべてカウントするとは限らないため、変更された行数が正確な値でない場合があります。

トランザクション数の上限

Cloud Spanner は、パーティション化 DML ステートメントの実行に必要なパーティションとトランザクションを作成します。トランザクション数の上限やトランザクションごとの同時実行の制限が適用されますが、Cloud Spanner はトランザクションがこの制限内に収まるように調整します。

Cloud Spanner では、データベースごとに最大 20,000 までのパーティション化 DML ステートメントを同時に実行できます。

サポートされていない機能

Cloud Spanner は、パーティション化 DML の一部の機能をサポートしていません。

  • INSERT はサポートされていません。
  • Cloud Console: Cloud Console でパーティション化 DML ステートメントを実行することはできません。
  • クエリプランとプロファイリング: gcloud コマンドライン ツールとクライアント ライブラリは、クエリプランとプロファイリングをサポートしていません。

次のコード例は、Albums テーブルの MarketingBudget 列を更新します。

C++

ExecutePartitionedDml() 関数を使用して、パーティション化 DML ステートメントを実行します。

void DmlPartitionedUpdate(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto result = client.ExecutePartitionedDml(
      spanner::SqlStatement("UPDATE Albums SET MarketingBudget = 100000"
                            " WHERE SingerId > 1"));
  if (!result) throw std::runtime_error(result.status().message());
  std::cout << "Update was successful [spanner_dml_partitioned_update]\n";
}

C#

ExecutePartitionedUpdateAsync() メソッドを使用して、パーティション化 DML ステートメントを実行します。

public static async Task UpdateUsingPartitionedDmlCoreAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    string connectionString =
        $"Data Source=projects/{projectId}/instances/{instanceId}"
        + $"/databases/{databaseId}";

    // Create connection to Cloud Spanner.
    using (var connection =
        new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        SpannerCommand cmd = connection.CreateDmlCommand(
            "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"
        );
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();
        Console.WriteLine($"{rowCount} row(s) updated...");
    }
}

Go

PartitionedUpdate() メソッドを使用して、パーティション化 DML ステートメントを実行します。


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func updateUsingPartitionedDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"}
	rowCount, err := client.PartitionedUpdate(ctx, stmt)
	if err != nil {
		return err
	}
	fmt.Fprintf(w, "%d record(s) updated.\n", rowCount)
	return nil
}

Java

executePartitionedUpdate() メソッドを使用して、パーティション化 DML ステートメントを実行します。

static void updateUsingPartitionedDml(DatabaseClient dbClient) {
  String sql = "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1";
  long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
  System.out.printf("%d records updated.\n", rowCount);
}

Node.js

runPartitionedUpdate() メソッドを使用して、パーティション化 DML ステートメントを実行します。

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

try {
  const [rowCount] = await database.runPartitionedUpdate({
    sql: 'UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1',
  });
  console.log(`Successfully updated ${rowCount} records.`);
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

executePartitionedUpdate() メソッドを使用して、パーティション化 DML ステートメントを実行します。

use Google\Cloud\Spanner\SpannerClient;

/**
 * Updates sample data in the database by partition with a DML statement.
 *
 * This updates the `MarketingBudget` column which must be created before
 * running this sample. You can add the column by running the `add_column`
 * sample or by running this DDL statement against your database:
 *
 *     ALTER TABLE Albums ADD COLUMN MarketingBudget INT64
 *
 * Example:
 * ```
 * update_data($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function update_data_with_partitioned_dml($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $rowCount = $database->executePartitionedUpdate(
        "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"
    );

    printf('Updated %d row(s).' . PHP_EOL, $rowCount);
}

Python

execute_partitioned_dml() メソッドを使用して、パーティション化 DML ステートメントを実行します。

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"

spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

row_ct = database.execute_partitioned_dml(
    "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"
)

print("{} records updated.".format(row_ct))

Ruby

execute_partitioned_update() メソッドを使用して、パーティション化 DML ステートメントを実行します。

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

row_count = client.execute_partition_update(
  "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"
)

puts "#{row_count} records updated."

次のコード例では、SingerId 列に基づいて Singers テーブルから行を削除します。

C++

void DmlPartitionedDelete(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto result = client.ExecutePartitionedDml(
      spanner::SqlStatement("DELETE FROM Singers WHERE SingerId > 10"));
  if (!result) throw std::runtime_error(result.status().message());
  std::cout << "Delete was successful [spanner_dml_partitioned_delete]\n";
}

C#

public static async Task DeleteUsingPartitionedDmlCoreAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    string connectionString =
        $"Data Source=projects/{projectId}/instances/{instanceId}"
        + $"/databases/{databaseId}";

    // Create connection to Cloud Spanner.
    using (var connection =
        new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        SpannerCommand cmd = connection.CreateDmlCommand(
            "DELETE FROM Singers WHERE SingerId > 10"
        );
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();
        Console.WriteLine($"{rowCount} row(s) deleted...");
    }
}

Go


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func deleteUsingPartitionedDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: "DELETE FROM Singers WHERE SingerId > 10"}
	rowCount, err := client.PartitionedUpdate(ctx, stmt)
	if err != nil {
		return err

	}
	fmt.Fprintf(w, "%d record(s) deleted.", rowCount)
	return nil
}

Java

static void deleteUsingPartitionedDml(DatabaseClient dbClient) {
  String sql = "DELETE FROM Singers WHERE SingerId > 10";
  long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
  System.out.printf("%d records deleted.\n", rowCount);
}

Node.js

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

try {
  const [rowCount] = await database.runPartitionedUpdate({
    sql: 'DELETE FROM Singers WHERE SingerId > 10',
  });
  console.log(`Successfully deleted ${rowCount} records.`);
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Delete sample data in the database by partition with a DML statement.
 *
 * This updates the `MarketingBudget` column which must be created before
 * running this sample. You can add the column by running the `add_column`
 * sample or by running this DDL statement against your database:
 *
 *     ALTER TABLE Albums ADD COLUMN MarketingBudget INT64
 *
 * Example:
 * ```
 * update_data($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function delete_data_with_partitioned_dml($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $rowCount = $database->executePartitionedUpdate(
        "DELETE FROM Singers WHERE SingerId > 10"
    );

    printf('Deleted %d row(s).' . PHP_EOL, $rowCount);
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

row_ct = database.execute_partitioned_dml("DELETE FROM Singers WHERE SingerId > 10")

print("{} record(s) deleted.".format(row_ct))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

row_count = client.execute_partition_update(
  "DELETE FROM Singers WHERE SingerId > 10"
)

puts "#{row_count} records deleted."

次のステップ