Stempel waktu commit di database dialek GoogleSQL

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

Ringkasan

Stempel waktu commit, berdasarkan teknologi TrueTime, adalah waktu saat 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 membuat fitur seperti log perubahan.

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

  1. Buat kolom dengan jenis TIMESTAMP dengan opsi kolom allow_commit_timestamp yang 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 penerapan.

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

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

Nilai stempel waktu commit tidak dijamin 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 di kolom TIMESTAMP.

Membuat dan menghapus kolom stempel waktu commit

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

  • Saat membuat tabel baru untuk menentukan bahwa kolom mendukung stempel waktu penerapan.
  • Saat mengubah tabel yang ada:
    • untuk menambahkan kolom baru yang mendukung stempel waktu commit,
    • untuk mengubah kolom TIMESTAMP yang ada agar 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) - Kunci menaik ideal untuk menjawab kueri dari waktu tertentu ke depan.
  • DESC - Tombol menurun akan mempertahankan baris terbaru di bagian atas tabel. Mereka menyediakan akses cepat ke data terbaru.

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

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

  • Kolom stempel waktu penerapan sebagai bagian pertama dari kunci utama tabel:

    CREATE TABLE Users (
      LastAccess TIMESTAMP NOT NULL,
      UserId     INT64 NOT NULL,
      ...
    ) PRIMARY KEY (LastAccess, UserId);
    
  • Bagian pertama 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 penerapan diaktifkan pada kolom non-kunci yang tidak diindeks.

Membuat kolom stempel waktu commit

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

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 pengganti spanner.commit_timestamp() (atau konstanta yang disediakan oleh library klien) untuk penyisipan dan update.
  • Kolom hanya dapat berisi nilai di 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 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 tindakan ini mengharuskan Spanner memvalidasi bahwa nilai stempel waktu yang ada sudah berlalu. 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 tersebut masih berupa stempel waktu. Mengubah opsi tidak akan mengubah karakteristik kolom lainnya, seperti jenis atau kemampuan null (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 transaksi di-commit.

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 penerapan 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 penerapan hanya dapat ditulis ke kolom yang diberi anotasi dengan opsi allow_commit_timestamp=true.

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

Membuat kueri kolom stempel waktu commit

Contoh berikut membuat kueri kolom stempel waktu penerapan 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

Memberikan nilai Anda sendiri untuk kolom stempel waktu commit

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

Membuat log perubahan

Misalkan Anda ingin membuat log perubahan setiap mutasi yang terjadi pada tabel, lalu menggunakan log perubahan tersebut untuk audit. Contohnya adalah tabel yang menyimpan histori perubahan pada dokumen pengolah kata. Stempel waktu commit mempermudah pembuatan log perubahan, karena stempel waktu dapat menerapkan pengurutan entri log perubahan. Anda dapat membuat log perubahan yang menyimpan histori perubahan pada dokumen tertentu menggunakan skema seperti 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 dengan saat Anda menyisipkan atau memperbarui baris di Document. Saat menyisipkan 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. Menyisipkan tabel DocumentsHistory dengan tabel Documents akan memungkinkan lokalitas data serta penyisipan dan pembaruan yang lebih efisien. Namun, tindakan ini juga menambahkan batasan bahwa baris induk dan turunan harus dihapus bersama. Untuk mempertahankan baris di DocumentHistory setelah baris di Documents dihapus, jangan selang-selingi tabel.

Mengoptimalkan kueri data terbaru dengan stempel waktu commit

Stempel waktu penerapan memungkinkan 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 konstanta: literal, parameter, atau fungsi yang argumennya sendiri dievaluasi ke konstanta.

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

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

Misalnya, pertimbangkan tabel Performances berikut, yang mencakup 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 diuntungkan dari pengoptimalan stempel waktu commit yang dijelaskan sebelumnya, karena memiliki perbandingan lebih besar dari atau sama dengan antara kolom stempel waktu commit tabel dan ekspresi konstanta—dalam hal ini, literal:

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

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

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

Langkah berikutnya