Meng-commit stempel waktu di database dialek GoogleSQL

Topik ini menjelaskan cara menulis stempel waktu commit untuk setiap operasi penyisipan dan pembaruan yang Anda lakukan dengan Spanner. Untuk menggunakan fitur ini, tetapkan opsi allow_commit_timestamp di kolom TIMESTAMP, lalu tulis stempel waktu sebagai bagian dari setiap transaksi.

Ringkasan

Stempel waktu commit, berdasarkan teknologi TrueTime, adalah waktu ketika transaksi di-commit dalam database. Opsi kolom allow_commit_timestamp memungkinkan Anda menyimpan stempel waktu commit ke dalam kolom secara atomik. Dengan menggunakan stempel waktu commit yang disimpan dalam tabel, Anda dapat menentukan urutan mutasi yang tepat dan fitur build seperti log perubahan.

Untuk menyisipkan stempel waktu commit ke database Anda, selesaikan langkah-langkah berikut:

  1. Buat kolom dengan jenis TIMESTAMP dengan opsi kolom allow_commit_timestamp ditetapkan ke true dalam definisi skema. Misalnya:

    CREATE TABLE Performances (
        ...
        LastUpdateTime  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
        ...
    ) PRIMARY KEY (...);
    
  2. Jika Anda melakukan penyisipan atau pembaruan dengan DML, gunakan fungsi PENDING_COMMIT_TIMESTAMP untuk menulis stempel waktu commit.

    Jika Anda melakukan penyisipan atau pembaruan dengan mutasi, gunakan string placeholder spanner.commit_timestamp() pada penyisipan atau pembaruan pada kolom stempel waktu commit. Anda juga dapat menggunakan konstanta stempel waktu commit yang disediakan oleh library klien. Misalnya, konstanta pada klien Java ini adalah Value.COMMIT_TIMESTAMP.

Saat Spanner meng-commit transaksi menggunakan placeholder ini sebagai nilai kolom, stempel waktu commit yang sebenarnya akan ditulis ke kolom yang ditentukan (Misalnya: kolom LastUpdateTime). Anda kemudian dapat menggunakan nilai kolom ini untuk membuat riwayat pembaruan pada tabel.

Nilai stempel waktu commit tidak dijamin akan unik. Transaksi yang menulis ke kumpulan kolom yang tidak tumpang-tindih mungkin memiliki stempel waktu yang sama. Transaksi yang menulis ke kumpulan kolom yang tumpang tindih memiliki stempel waktu yang unik.

Stempel waktu commit Spanner memiliki perincian mikrodetik, dan dikonversi menjadi nanodetik saat disimpan dalam TIMESTAMP kolom.

Membuat dan menghapus kolom stempel waktu commit

Gunakan opsi kolom allow_commit_timestamp untuk menambahkan dan menghapus dukungan bagi stempel waktu commit:

  • Saat membuat tabel baru untuk menentukan bahwa kolom mendukung stempel waktu commit.
  • Saat mengubah tabel yang ada:
    • untuk menambahkan kolom baru yang mendukung stempel waktu commit,
    • untuk mengubah kolom TIMESTAMP yang ada guna mendukung stempel waktu commit,
    • untuk mengubah kolom TIMESTAMP yang ada guna menghapus dukungan stempel waktu commit

Kunci dan indeks

Anda dapat menggunakan kolom stempel waktu commit sebagai kolom kunci utama atau sebagai kolom non-kunci. Kunci utama dapat ditentukan sebagai ASC atau DESC.

  • ASC (default) - Tombol menaik ideal untuk menjawab kueri dari waktu tertentu ke depan.
  • DESC - Kunci menurun menyimpan baris terbaru di bagian atas tabel. Dasbor ini memberikan akses cepat ke data terbaru.

Opsi allow_commit_timestamp harus konsisten di seluruh kunci utama tabel induk dan turunan. Jika opsi ini tidak konsisten di seluruh kunci utama, Spanner akan menampilkan error. Satu-satunya situasi ketika opsi menjadi tidak konsisten adalah saat Anda membuat atau memperbarui skema.

Menggunakan stempel waktu commit dalam skenario berikut akan membuat hotspot yang mengurangi performa data:

  • Jalankan kolom stempel waktu sebagai bagian pertama kunci utama tabel:

    CREATE TABLE Users (
      LastAccess TIMESTAMP NOT NULL,
      UserId     INT64 NOT NULL,
      ...
    ) PRIMARY KEY (LastAccess, UserId);
    
  • Bagian pertama dari kunci utama indeks sekunder:

    CREATE INDEX UsersByLastAccess ON Users(LastAccess)
    

    atau

    CREATE INDEX UsersByLastAccessAndName ON Users(LastAccess, FirstName)
    

Hotspot mengurangi performa data, bahkan dengan kecepatan tulis yang rendah. Tidak ada overhead performa jika stempel waktu commit diaktifkan pada kolom non-kunci yang tidak diindeks.

Membuat kolom stempel waktu commit

DDL berikut membuat tabel dengan kolom yang mendukung stempel waktu commit.

CREATE TABLE Performances (
    SingerId        INT64 NOT NULL,
    VenueId         INT64 NOT NULL,
    EventDate       Date,
    Revenue         INT64,
    LastUpdateTime  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (SingerId, VenueId, EventDate),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE

Menambahkan opsi akan mengubah kolom stempel waktu sebagai berikut:

  • Anda dapat menggunakan string placeholder spanner.commit_timestamp() (atau konstanta yang disediakan oleh library klien) untuk penyisipan dan update.
  • Kolom hanya boleh berisi nilai pada masa lalu. Untuk mengetahui informasi selengkapnya, lihat Memberikan nilai Anda sendiri untuk stempel waktu.

Opsi allow_commit_timestamp peka huruf besar/kecil.

Menambahkan kolom stempel waktu commit ke tabel yang ada

Untuk menambahkan kolom stempel waktu commit ke tabel yang sudah ada, gunakan pernyataan ALTER TABLE. Misalnya untuk menambahkan kolom LastUpdateTime ke tabel Performances, gunakan pernyataan berikut:

ALTER TABLE Performances ADD COLUMN LastUpdateTime TIMESTAMP
    NOT NULL OPTIONS (allow_commit_timestamp=true)

Mengonversi kolom stempel waktu menjadi kolom stempel waktu commit

Anda dapat mengonversi kolom stempel waktu yang ada menjadi kolom stempel waktu commit, tetapi melakukan hal ini memerlukan Spanner untuk memvalidasi bahwa nilai stempel waktu yang ada berada di masa lalu. Contoh:

ALTER TABLE Performances ALTER COLUMN LastUpdateTime
    SET OPTIONS (allow_commit_timestamp=true)

Anda tidak dapat mengubah jenis data atau anotasi NULL kolom dalam pernyataan ALTER TABLE yang menyertakan SET OPTIONS. Untuk mengetahui detailnya, lihat Bahasa Definisi Data.

Menghapus opsi stempel waktu commit

Jika Anda ingin menghapus dukungan stempel waktu commit dari kolom, gunakan opsi allow_commit_timestamp=null dalam pernyataan ALTER TABLE. Perilaku stempel waktu commit dihapus, tetapi kolom masih merupakan stempel waktu. Mengubah opsi ini tidak akan mengubah karakteristik lain dari kolom, seperti jenis atau nullability (NOT NULL). Misalnya:

ALTER TABLE Performances ALTER COLUMN LastUpdateTime
    SET OPTIONS (allow_commit_timestamp=null)

Menulis stempel waktu commit menggunakan pernyataan DML

Anda menggunakan fungsi PENDING_COMMIT_TIMESTAMP untuk menulis stempel waktu commit dalam pernyataan DML. Spanner memilih stempel waktu commit saat commit transaksi.

Pernyataan DML berikut memperbarui kolom LastUpdateTime di tabel Performances dengan stempel waktu commit:

UPDATE Performances SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP()
   WHERE SingerId=1 AND VenueId=2 AND EventDate="2015-10-21"

Contoh kode berikut menggunakan fungsi PENDING_COMMIT_TIMESTAMP untuk menulis stempel waktu commit di kolom LastUpdateTime.

C++

void DmlStandardUpdateWithTimestamp(google::cloud::spanner::Client client) {
  using ::google::cloud::StatusOr;
  namespace spanner = ::google::cloud::spanner;
  auto commit_result = client.Commit(
      [&client](spanner::Transaction txn) -> StatusOr<spanner::Mutations> {
        auto update = client.ExecuteDml(
            std::move(txn),
            spanner::SqlStatement(
                "UPDATE Albums SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP()"
                "  WHERE SingerId = 1"));
        if (!update) return std::move(update).status();
        return spanner::Mutations{};
      });
  if (!commit_result) throw std::move(commit_result).status();
  std::cout << "Update was successful "
            << "[spanner_dml_standard_update_with_timestamp]\n";
}

C#


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

public class UpdateUsingDmlWithTimestampCoreAsyncSample
{
    public async Task<int> UpdateUsingDmlWithTimestampCoreAsync(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 LastUpdateTime = PENDING_COMMIT_TIMESTAMP() WHERE SingerId = 1");
        int rowCount = await cmd.ExecuteNonQueryAsync();

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

Go


import (
	"context"
	"fmt"
	"io"

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

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

	_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `UPDATE Albums
				SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP()
				WHERE SingerId = 1`,
		}
		rowCount, err := txn.Update(ctx, stmt)
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "%d record(s) updated.\n", rowCount)
		return nil
	})
	return err
}

Java

static void updateUsingDmlWithTimestamp(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(transaction -> {
        String sql =
            "UPDATE Albums "
                + "SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP() WHERE SingerId = 1";
        long rowCount = transaction.executeUpdate(Statement.of(sql));
        System.out.printf("%d records updated.\n", rowCount);
        return null;
      });
}

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);

database.runTransaction(async (err, transaction) => {
  if (err) {
    console.error(err);
    return;
  }
  try {
    const [rowCount] = await transaction.runUpdate({
      sql: `UPDATE Albums
        SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP()
        WHERE SingerId = 1`,
    });

    console.log(`Successfully updated ${rowCount} records.`);
    await transaction.commit();
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    // Close the database when finished.
    database.close();
  }
});

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;

/**
 * Update data with a DML statement using timestamps.
 *
 * The database and table must already exist and can be created using
 * `create_database`.
 * Example:
 * ```
 * insert_data($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function update_data_with_dml_timestamp(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $database->runTransaction(function (Transaction $t) {
        $rowCount = $t->executeUpdate(
            'UPDATE Albums '
            . 'SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP() WHERE SingerId = 1');
        $t->commit();
        printf('Updated %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)

def update_albums(transaction):
    row_ct = transaction.execute_update(
        "UPDATE Albums "
        "SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP() "
        "WHERE SingerId = 1"
    )

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

database.run_in_transaction(update_albums)

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 = 0

client.transaction do |transaction|
  row_count = transaction.execute_update(
    "UPDATE Albums SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP() WHERE SingerId = 1"
  )
end

puts "#{row_count} records updated."

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

commit_timestamp = client.commit_timestamp

client.commit do |c|
  c.update "Albums", [
    { SingerId: 1, AlbumId: 1, MarketingBudget: 100_000, LastUpdateTime: commit_timestamp },
    { SingerId: 2, AlbumId: 2, MarketingBudget: 750_000, LastUpdateTime: commit_timestamp }
  ]
end

puts "Updated data"

Stempel waktu commit hanya dapat ditulis ke kolom yang dianotasikan dengan opsi allow_commit_timestamp=true.

Jika memiliki mutasi pada baris dalam beberapa tabel, Anda harus menentukan spanner.commit_timestamp() (atau konstanta library klien) untuk kolom stempel waktu commit di setiap tabel.

Mengkueri kolom stempel waktu commit

Contoh berikut mengkueri kolom stempel waktu commit dalam tabel.

C++

void QueryDataWithTimestamp(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;

  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime"
      "  FROM Albums"
      " ORDER BY LastUpdateTime DESC");
  using RowType =
      std::tuple<std::int64_t, std::int64_t, absl::optional<std::int64_t>,
                 absl::optional<spanner::Timestamp>>;

  auto rows = client.ExecuteQuery(std::move(select));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << std::get<0>(*row) << " " << std::get<1>(*row);
    auto marketing_budget = std::get<2>(*row);
    if (!marketing_budget) {
      std::cout << " NULL";
    } else {
      std::cout << ' ' << *marketing_budget;
    }
    auto last_update_time = std::get<3>(*row);
    if (!last_update_time) {
      std::cout << " NULL";
    } else {
      std::cout << ' ' << *last_update_time;
    }
    std::cout << "\n";
  }
}

C#


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

public class QueryDataWithTimestampColumnAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public DateTime? LastUpdateTime { get; set; }
        public long? MarketingBudget { get; set; }
    }

    public async Task<List<Album>> QueryDataWithTimestampColumnAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime FROM Albums ORDER BY LastUpdateTime DESC");

        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                LastUpdateTime = reader.IsDBNull(reader.GetOrdinal("LastUpdateTime")) ? (DateTime?)null : reader.GetFieldValue<DateTime>("LastUpdateTime"),
                MarketingBudget = reader.IsDBNull(reader.GetOrdinal("MarketingBudget")) ? 0 : reader.GetFieldValue<long>("MarketingBudget")
            });
        }
        return albums;
    }
}

Go


import (
	"context"
	"fmt"
	"io"
	"strconv"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

func queryWithTimestamp(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: `SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime
				FROM Albums ORDER BY LastUpdateTime DESC`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var marketingBudget spanner.NullInt64
		var lastUpdateTime spanner.NullTime
		if err := row.ColumnByName("SingerId", &singerID); err != nil {
			return err
		}
		if err := row.ColumnByName("AlbumId", &albumID); err != nil {
			return err
		}
		if err := row.ColumnByName("MarketingBudget", &marketingBudget); err != nil {
			return err
		}
		budget := "NULL"
		if marketingBudget.Valid {
			budget = strconv.FormatInt(marketingBudget.Int64, 10)
		}
		if err := row.ColumnByName("LastUpdateTime", &lastUpdateTime); err != nil {
			return err
		}
		timestamp := "NULL"
		if lastUpdateTime.Valid {
			timestamp = lastUpdateTime.String()
		}
		fmt.Fprintf(w, "%d %d %s %s\n", singerID, albumID, budget, timestamp)
	}
}

Java

static void queryMarketingBudgetWithTimestamp(DatabaseClient dbClient) {
  // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
  // null. A try-with-resource block is used to automatically release resources held by
  // ResultSet.
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .executeQuery(
              Statement.of(
                  "SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime FROM Albums"
                      + " ORDER BY LastUpdateTime DESC"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s %s\n",
          resultSet.getLong("SingerId"),
          resultSet.getLong("AlbumId"),
          // We check that the value is non null. ResultSet getters can only be used to retrieve
          // non null values.
          resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"),
          resultSet.isNull("LastUpdateTime") ? "NULL" : resultSet.getTimestamp("LastUpdateTime"));
    }
  }
}

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);

const query = {
  sql: `SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime
          FROM Albums ORDER BY LastUpdateTime DESC`,
};

// Queries rows from the Albums table
try {
  const [rows] = await database.run(query);

  rows.forEach(row => {
    const json = row.toJSON();

    console.log(
      `SingerId: ${json.SingerId}, AlbumId: ${
        json.AlbumId
      }, MarketingBudget: ${
        json.MarketingBudget ? json.MarketingBudget : null
      }, LastUpdateTime: ${json.LastUpdateTime}`
    );
  });
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished
  database.close();
}

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Queries sample data from a database with a commit timestamp column.
 *
 * This sample uses the `MarketingBudget` column. 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
 *
 * This sample also uses the 'LastUpdateTime' commit timestamp column. You can
 * add the column by running the `add_timestamp_column` sample or by running
 * this DDL statement against your database:
 *
 * 		ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true)
 *
 * Example:
 * ```
 * query_data_with_timestamp_column($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function query_data_with_timestamp_column(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $results = $database->execute(
        'SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime ' .
        ' FROM Albums ORDER BY LastUpdateTime DESC'
    );

    foreach ($results as $row) {
        if ($row['MarketingBudget'] == null) {
            $row['MarketingBudget'] = 'NULL';
        }
        if ($row['LastUpdateTime'] == null) {
            $row['LastUpdateTime'] = 'NULL';
        }
        printf('SingerId: %s, AlbumId: %s, MarketingBudget: %s, LastUpdateTime: %s' . PHP_EOL,
            $row['SingerId'], $row['AlbumId'], $row['MarketingBudget'], $row['LastUpdateTime']);
    }
}

Python

def query_data_with_timestamp(instance_id, database_id):
    """Queries sample data from the database using SQL.

    This updates the `LastUpdateTime` column which must be created before
    running this sample. You can add the column by running the
    `add_timestamp_column` sample or by running this DDL statement
    against your database:

        ALTER TABLE Performances ADD COLUMN LastUpdateTime TIMESTAMP
        OPTIONS (allow_commit_timestamp=true)

    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)

    database = instance.database(database_id)

    with database.snapshot() as snapshot:
        results = snapshot.execute_sql(
            "SELECT SingerId, AlbumId, MarketingBudget FROM Albums "
            "ORDER BY LastUpdateTime DESC"
        )

    for row in results:
        print("SingerId: {}, AlbumId: {}, MarketingBudget: {}".format(*row))

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

client.execute("SELECT SingerId, AlbumId, MarketingBudget, LastUpdateTime
                FROM Albums ORDER BY LastUpdateTime DESC").rows.each do |row|
  puts "#{row[:SingerId]} #{row[:AlbumId]} #{row[:MarketingBudget]} #{row[:LastUpdateTime]}"
end

Berikan nilai Anda sendiri untuk kolom stempel waktu commit

Anda dapat memberikan nilai Anda sendiri untuk kolom stempel waktu commit, dan bukan meneruskan spanner.commit_timestamp() (atau konstanta library klien) sebagai nilai kolom. Nilai harus berupa stempel waktu di masa lalu. Batasan ini memastikan bahwa penulisan stempel waktu adalah operasi yang murah dan cepat. Server menampilkan error FailedPrecondition jika stempel waktu yang akan datang ditentukan.

Membuat {i>changelog<i}

Anggaplah Anda ingin membuat log perubahan untuk setiap mutasi yang terjadi pada tabel, lalu menggunakan log perubahan tersebut untuk mengaudit. Contohnya adalah tabel yang menyimpan riwayat perubahan pada dokumen pengolahan kata. Stempel waktu commit mempermudah pembuatan log perubahan, karena stempel waktu dapat memberlakukan pengurutan entri log perubahan. Anda dapat membuat log perubahan yang menyimpan histori perubahan pada dokumen tertentu menggunakan skema seperti dalam contoh berikut:

CREATE TABLE Documents (
  UserId     INT64 NOT NULL,
  DocumentId INT64 NOT NULL,
  Contents   STRING(MAX) NOT NULL,
) PRIMARY KEY (UserId, DocumentId);

CREATE TABLE DocumentHistory (
  UserId     INT64 NOT NULL,
  DocumentId INT64 NOT NULL,
  Ts         TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
  Delta      STRING(MAX),
) PRIMARY KEY (UserId, DocumentId, Ts),
  INTERLEAVE IN PARENT Documents ON DELETE NO ACTION;

Untuk membuat log perubahan, sisipkan baris baru di DocumentHistory dalam transaksi yang sama tempat Anda menyisipkan atau memperbarui baris di Document. Dalam penyisipan baris baru di DocumentHistory, gunakan placeholder spanner.commit_timestamp() (atau konstanta library klien) untuk memberi tahu Spanner agar menulis stempel waktu commit ke kolom Ts. Menggabungkan tabel DocumentsHistory dengan tabel Documents akan memungkinkan lokalitas data serta penyisipan dan pembaruan yang lebih efisien. Namun, cara ini juga menambahkan batasan sehingga baris induk dan turunan harus dihapus bersama. Untuk mempertahankan baris di DocumentHistory setelah baris di Documents dihapus, jangan menyisipkan tabel.

Mengoptimalkan kueri data terbaru dengan stempel waktu commit

Commit stempel waktu mengaktifkan pengoptimalan Spanner yang dapat mengurangi I/O kueri saat mengambil data yang ditulis setelah waktu tertentu.

Untuk mengaktifkan pengoptimalan ini, klausa WHERE kueri harus menyertakan perbandingan antara kolom stempel waktu commit tabel dan waktu tertentu yang Anda berikan, dengan atribut berikut:

  • Berikan waktu tertentu sebagai ekspresi konstan: literal, parameter, atau fungsi yang argumennya sendiri dievaluasi ke konstanta.

  • Bandingkan apakah stempel waktu commit lebih baru daripada waktu tertentu, melalui operator > atau >=.

  • Secara opsional, tambahkan pembatasan lebih lanjut ke klausa WHERE dengan AND. Memperluas klausa dengan OR akan mendiskualifikasi kueri dari pengoptimalan ini.

Misalnya, perhatikan tabel Performances berikut, yang menyertakan kolom stempel waktu commit:

CREATE TABLE Performances (
    SingerId INT64 NOT NULL,
    VenueId INT64 NOT NULL,
    EventDate DATE,
    Revenue INT64,
    LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (SingerId, VenueId, EventDate);

Kueri ini mendapatkan manfaat dari pengoptimalan stempel waktu commit yang dijelaskan sebelumnya, karena memiliki perbandingan yang lebih besar dari atau sama dengan antara kolom stempel waktu commit tabel dan ekspresi konstan—dalam hal ini, literal:

SELECT * FROM Performances WHERE LastUpdateTime >= "2022-05-01";

Kueri berikut juga memenuhi syarat untuk pengoptimalan, karena memiliki perbandingan yang lebih besar dari stempel waktu commit dan fungsi yang semua argumennya dievaluasi ke konstanta selama eksekusi kueri:

SELECT * FROM Performances
  WHERE LastUpdateTime > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY);

Langkah selanjutnya