Bahasa Manipulasi Data Berpartisi

Bahasa Manipulasi Data (DML) terpartisi (DML terpartisi) dirancang untuk jenis pembaruan dan penghapusan massal berikut:

  • Pembersihan berkala dan pembersihan sampah memori. Contohnya adalah menghapus baris lama atau menetapkan kolom ke NULL.
  • Mengisi ulang kolom baru dengan nilai default. Contohnya adalah menggunakan pernyataan UPDATE untuk menetapkan nilai kolom baru ke False, yang saat ini adalah NULL.

DML yang dipartisi tidak cocok untuk pemrosesan transaksi skala kecil. Jika Anda ingin menjalankan pernyataan pada beberapa baris, gunakan DML transaksional dengan kunci utama yang dapat diidentifikasi. Untuk mengetahui informasi selengkapnya, lihat Menggunakan DML.

Jika perlu melakukan operasi tulis buta dalam jumlah besar, tetapi tidak memerlukan transaksi atomik, Anda dapat mengubah tabel Spanner secara massal menggunakan operasi tulis batch. Untuk mengetahui informasi selengkapnya, lihat Mengubah data menggunakan operasi tulis batch.

Anda bisa mendapatkan insight tentang kueri DML terpartisi aktif dan progresnya dari tabel statistik di database Spanner. Untuk mengetahui informasi selengkapnya, lihat Statistik DML yang dipartisi aktif.

DML dan DML yang dipartisi

Spanner mendukung dua mode eksekusi untuk pernyataan DML:

  • DML, yang cocok untuk pemrosesan transaksi. Untuk mengetahui informasi selengkapnya, lihat Menggunakan DML.

  • DML yang dipartisi, yang memungkinkan operasi skala besar di seluruh database dengan dampak minimal pada pemrosesan transaksi serentak dengan mempartisi ruang kunci dan menjalankan pernyataan di seluruh partisi dalam transaksi cakupan yang lebih kecil dan terpisah. Untuk mengetahui informasi selengkapnya, lihat Menggunakan DML berpartisi.

Tabel berikut menyoroti beberapa perbedaan antara kedua mode eksekusi.

DML DML yang dipartisi
Baris yang tidak cocok dengan klausa WHERE mungkin dikunci. Hanya baris yang cocok dengan klausa WHERE yang dikunci.
Batas ukuran transaksi berlaku. Spanner menangani batas transaksi dan batas konkurensi per transaksi.
Pernyataan tidak perlu idempoten. Pernyataan DML harus idempoten untuk memastikan hasil yang konsisten.
Transaksi dapat mencakup beberapa pernyataan DML dan SQL. Transaksi yang dipartisi hanya dapat menyertakan satu pernyataan DML.
Tidak ada batasan pada kompleksitas pernyataan. Pernyataan harus dapat dipartisi sepenuhnya.
Anda membuat transaksi baca-tulis dalam kode klien. Spanner membuat transaksi.

Dapat dipartisi dan idempoten

Saat pernyataan DML berpartisi berjalan, baris di satu partisi tidak memiliki akses ke baris di partisi lain, dan Anda tidak dapat memilih cara Spanner membuat partisi. Partisi memastikan skalabilitas, tetapi juga berarti bahwa pernyataan DML yang dipartisi harus dapat dipartisi sepenuhnya. Artinya, pernyataan DML yang dipartisi harus dapat dinyatakan sebagai gabungan dari serangkaian pernyataan, dengan setiap pernyataan mengakses satu baris tabel dan setiap pernyataan tidak mengakses tabel lain. Misalnya, pernyataan DML yang mengakses beberapa tabel atau melakukan join mandiri tidak dapat dipartisi. Jika pernyataan DML tidak dapat dipartisi, Spanner akan menampilkan error BadUsage.

Pernyataan DML ini sepenuhnya dapat dipartisi, karena setiap pernyataan dapat diterapkan ke satu baris dalam tabel:

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

DELETE FROM Albums WHERE MarketingBudget > 10000;

Pernyataan DML ini tidak dapat dipartisi sepenuhnya, karena mengakses beberapa tabel:

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

Spanner mungkin menjalankan pernyataan DML yang dipartisi beberapa kali terhadap beberapa partisi karena percobaan ulang tingkat jaringan. Akibatnya, pernyataan mungkin dijalankan lebih dari sekali pada baris. Oleh karena itu, pernyataan harus idempoten untuk menghasilkan hasil yang konsisten. Pernyataan bersifat idempoten jika menjalankannya beberapa kali terhadap satu baris menghasilkan hasil yang sama.

Pernyataan DML ini bersifat idempoten:

UPDATE Singers SET MarketingBudget = 1000 WHERE true;

Pernyataan DML ini tidak idempoten:

UPDATE Singers SET MarketingBudget = 1.5 * MarketingBudget WHERE true;

Penguncian baris

Spanner memperoleh kunci hanya jika baris adalah kandidat untuk pembaruan atau penghapusan. Perilaku ini berbeda dengan eksekusi DML, yang mungkin mengunci baris baca yang tidak cocok dengan klausa WHERE.

Eksekusi dan transaksi

Apakah pernyataan DML dipartisi atau tidak bergantung pada metode library klien yang Anda pilih untuk dieksekusi. Setiap library klien menyediakan metode terpisah untuk eksekusi DML dan eksekusi DML yang Dipartisi.

Anda hanya dapat menjalankan satu pernyataan DML yang dipartisi dalam panggilan ke metode library klien.

Spanner tidak menerapkan pernyataan DML yang dipartisi secara atomik di seluruh tabel. Namun, Spanner menerapkan pernyataan DML yang dipartisi secara atomik di setiap partisi.

DML yang dipartisi tidak mendukung commit atau rollback. Spanner segera mengeksekusi dan menerapkan pernyataan DML.

  • Jika Anda membatalkan operasi, Spanner akan membatalkan partisi yang sedang dijalankan dan tidak memulai partisi yang tersisa. Spanner tidak melakukan rollback pada partisi apa pun yang telah dijalankan.
  • Jika eksekusi pernyataan menyebabkan error, eksekusi akan berhenti di semua partisi dan Spanner akan menampilkan error tersebut untuk seluruh operasi. Beberapa contoh error adalah pelanggaran terhadap batasan jenis data, pelanggaran terhadap UNIQUE INDEX, dan pelanggaran terhadap ON DELETE NO ACTION. Bergantung pada titik waktu saat eksekusi gagal, pernyataan mungkin berhasil dijalankan pada beberapa partisi, dan mungkin tidak pernah dijalankan pada partisi lain.

Jika pernyataan DML terpartisi berhasil, Spanner akan menjalankan pernyataan setidaknya sekali terhadap setiap partisi rentang kunci.

Jumlah baris yang diubah

Pernyataan DML yang dipartisi menampilkan batas bawah pada jumlah baris yang diubah. Jumlah ini mungkin bukan jumlah persis dari jumlah baris yang diubah, karena tidak ada jaminan bahwa Spanner menghitung semua baris yang diubah.

Batas transaksi

Spanner membuat partisi dan transaksi yang diperlukan untuk menjalankan pernyataan DML terpartisi. Batas transaksi atau batas serentak per transaksi berlaku, tetapi Spanner mencoba menjaga transaksi dalam batas.

Spanner mengizinkan maksimum 20.000 pernyataan DML terpartisi serentak per database.

Fitur yang tidak didukung

Spanner tidak mendukung beberapa fitur untuk DML yang dipartisi:

  • INSERT tidak didukung.
  • Konsol Google Cloud: Anda tidak dapat menjalankan pernyataan DML yang dipartisi di konsol Google Cloud.
  • Rencana dan pembuatan profil kueri: Google Cloud CLI dan library klien tidak mendukung rencana dan pembuatan profil kueri.
  • Subkueri yang membaca dari tabel lain, atau baris yang berbeda dari tabel yang sama.

Untuk skenario yang kompleks, seperti memindahkan tabel atau transformasi yang memerlukan join di seluruh tabel, pertimbangkan untuk menggunakan konektor Dataflow.

Contoh

Contoh kode berikut memperbarui kolom MarketingBudget dari tabel Albums.

C++

Anda menggunakan fungsi ExecutePartitionedDml() untuk mengeksekusi pernyataan DML yang dipartisi.

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#

Anda menggunakan metode ExecutePartitionedUpdateAsync() untuk mengeksekusi pernyataan DML berpartisi.


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

Anda menggunakan metode PartitionedUpdate() untuk mengeksekusi pernyataan DML berpartisi.


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

Anda menggunakan metode executePartitionedUpdate() untuk mengeksekusi pernyataan DML berpartisi.

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

Anda menggunakan metode runPartitionedUpdate() untuk mengeksekusi pernyataan DML berpartisi.

// 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

Anda menggunakan metode executePartitionedUpdate() untuk mengeksekusi pernyataan DML berpartisi.

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

Anda menggunakan metode execute_partitioned_dml() untuk mengeksekusi pernyataan DML berpartisi.

# 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

Anda menggunakan metode execute_partitioned_update() untuk mengeksekusi pernyataan DML berpartisi.

# 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."

Contoh kode berikut menghapus baris dari tabel Singers, berdasarkan kolom SingerId.

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

Apa langkah selanjutnya?