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

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

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

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

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 コネクタの使用を検討してください。

Examples

次のコード例は、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 ステートメントを実行します。


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($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#


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($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."

次のステップ