Bahasa Manipulasi Data yang Dipartisi

Data Manipulation Language yang dipartisi (DML yang dipartisi) dirancang untuk jenis update 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 bernilai NULL.

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

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

Anda dapat memperoleh insight tentang kueri DML terpartisi aktif dan progresnya dari tabel statistik di database Spanner. Untuk mengetahui informasi selengkapnya, lihat Statistik DML terpartisi aktif.

DML dan DML terpartisi

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 seluruh database berskala besar dengan dampak minimal terhadap pemrosesan transaksi serentak dengan mempartisi ruang kunci dan menjalankan pernyataan melalui partisi dalam transaksi terpisah dengan cakupan yang lebih kecil. Untuk mengetahui informasi selengkapnya, lihat Menggunakan DML yang dipartisi.

Tabel berikut menyoroti beberapa perbedaan antara kedua mode eksekusi tersebut.

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 serentak per transaksi.
Pernyataan tidak harus idempoten. Pernyataan DML harus idempoten untuk memastikan hasil yang konsisten.
Sebuah 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 yang membuat transaksi.

Dapat dipartisi dan idempoten

Saat pernyataan DML terpartisi berjalan, baris dalam 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 dinyatakan sebagai gabungan dari kumpulan pernyataan, di mana setiap pernyataan mengakses satu baris tabel dan setiap pernyataan tidak mengakses tabel lain. Misalnya, pernyataan DML yang mengakses beberapa tabel atau melakukan penggabungan 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 sepenuhnya dapat dipartisi, karena dapat mengakses beberapa tabel:

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

Spanner dapat mengeksekusi pernyataan DML terpartisi beberapa kali terhadap beberapa partisi karena percobaan ulang tingkat jaringan. Akibatnya, pernyataan mungkin dijalankan lebih dari sekali terhadap satu baris. Oleh karena itu, pernyataan tersebut harus bersifat idempoten untuk memberikan hasil yang konsisten. Suatu pernyataan bersifat idempoten jika mengeksekusinya beberapa kali terhadap satu baris akan memberikan 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 hanya memperoleh kunci jika baris merupakan kandidat untuk pembaruan atau penghapusan. Perilaku ini berbeda dengan eksekusi DML, yang mungkin membaca-kunci baris 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 Terpartisi.

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

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

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

  • Jika Anda membatalkan operasi, Spanner akan membatalkan partisi eksekusi dan tidak memulai partisi yang tersisa. Spanner tidak me-roll back partisi yang telah dieksekusi.
  • 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 batasan jenis data, pelanggaran UNIQUE INDEX, dan pelanggaran ON DELETE NO ACTION. Bergantung pada waktu ketika eksekusi gagal, pernyataan tersebut mungkin telah berhasil dijalankan pada beberapa partisi, dan mungkin tidak pernah dijalankan pada partisi lain.

Jika pernyataan DML yang telah dipartisi berhasil, Spanner akan menjalankan pernyataan setidaknya satu kali pada 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 pasti dari jumlah baris yang diubah, karena tidak ada jaminan bahwa Spanner akan menghitung semua baris yang diubah.

Batas transaksi

Spanner membuat partisi dan transaksi yang diperlukan untuk mengeksekusi pernyataan DML yang dipartisi. Batas transaksi atau batas serentak per transaksi berlaku, tetapi Spanner berupaya menjaga transaksi agar tidak melebihi batas.

Spanner mengizinkan maksimum 20.000 pernyataan DML terpartisi secara serentak per database.

Fitur yang tidak didukung

Spanner tidak mendukung beberapa fitur untuk DML terpartisi:

  • INSERT tidak didukung.
  • Konsol Google Cloud: Anda tidak dapat mengeksekusi pernyataan DML terpartisi di konsol Google Cloud.
  • Paket kueri dan pembuatan profil: Google Cloud CLI dan library klien tidak mendukung paket kueri dan pembuatan profil.
  • {i>Subquery<i} yang membaca dari tabel lain, atau baris berbeda dari tabel yang sama.

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

Contoh

Contoh kode berikut memperbarui kolom MarketingBudget pada 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 yang dipartisi.


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 yang dipartisi.


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 yang dipartisi.

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 yang dipartisi.

// 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 yang dipartisi.

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 yang dipartisi.

# 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 yang dipartisi.

# 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?