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

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

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

パーティション化 DML は小規模なトランザクション処理には適していません。数行でステートメントを実行する場合は、識別可能な主キーでトランザクション DML を使用します。詳しくは、DML の使用をご覧ください。

多数の盲目的書き込みを commit する必要があるが、アトミック トランザクションは必要ない場合は、バッチ書き込みを使用して Spanner テーブルを一括変更できます。詳細については、バッチ書き込みを使用してデータを変更するをご覧ください。

Spanner データベースの統計情報テーブルから、アクティブなパーティション化 DML クエリとその進捗状況に関する分析情報を取得できます。詳細については、アクティブなパーティション化 DML の統計情報をご覧ください。

DML とパーティション化 DML

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

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

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

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

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

分割可能とべき等

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

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

UPDATE Singers SET LastName = NULL WHERE LastName = '';

DELETE FROM Albums WHERE MarketingBudget > 10000;

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

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

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

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

UPDATE Singers SET MarketingBudget = 1000 WHERE true;

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

UPDATE Singers SET MarketingBudget = 1.5 * MarketingBudget WHERE true;

行のロック

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

実行とトランザクション

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

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

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

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

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

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

変更された行の数

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

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

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

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

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

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

  • INSERT はサポートされていません。
  • Google Cloud コンソール: Google Cloud コンソールでパーティション化 DML ステートメントを実行することはできません。
  • クエリプランとプロファイリング: Google Cloud CLI とクライアント ライブラリは、クエリプランとプロファイリングをサポートしていません。
  • サブテーブル(別のテーブルまたは同じテーブルの別の行から読み取る)。

テーブル間の結合が必要なテーブルや変換などの複雑なシナリオでは、Dataflow コネクタの使用を検討してください。

次のコード例は、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::move(result).status();
  std::cout << "Updated at least " << result->row_count_lower_bound
            << " row(s) [spanner_dml_partitioned_update]\n";
}

C#

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


using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;

public class UpdateUsingPartitionedDmlCoreAsyncSample
{
    public async Task<long> UpdateUsingPartitionedDmlCoreAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        using var cmd = connection.CreateDmlCommand("UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1");
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();

        Console.WriteLine($"{rowCount} row(s) updated...");
        return rowCount;
    }
}

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(string $instanceId, string $databaseId): void
{
    $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::move(result).status();
  std::cout << "Deleted at least " << result->row_count_lower_bound
            << " row(s) [spanner_dml_partitioned_delete]\n";
}

C#


using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;

public class DeleteUsingPartitionedDmlCoreAsyncSample
{
    public async Task<long> DeleteUsingPartitionedDmlCoreAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        using var cmd = connection.CreateDmlCommand("DELETE FROM Singers WHERE SingerId > 10");
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();

        Console.WriteLine($"{rowCount} row(s) deleted...");
        return rowCount;
    }
}

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(string $instanceId, string $databaseId): void
{
    $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."

次のステップ