Carimbos de data/hora de confirmação em bancos de dados de dialetos GoogleSQL

Neste tópico, descrevemos como gravar um carimbo de data/hora de confirmação para cada operação de inserção e atualização executada com o Spanner. Para usar esse recurso, defina a opção allow_commit_timestamp em uma coluna TIMESTAMP e grave o carimbo de data/hora como parte de cada transação.

Visão geral

O carimbo de data/hora de confirmação, com base na tecnologia TrueTime, é o horário em que uma transação é confirmada no banco de dados. A opção de coluna allow_commit_timestamp permite armazenar atomicamente o carimbo de data/hora de confirmação em uma coluna. Usando os carimbos de data/hora de confirmação armazenados em tabelas, determine a ordem exata de mutações e recursos de versão como registros de alterações.

Para inserir carimbos de data/hora de confirmação no seu banco de dados, siga as etapas a seguir:

  1. Crie uma coluna com o tipo TIMESTAMP com a opção de coluna allow_commit_timestamp definida como true na definição do esquema. Exemplo:

    CREATE TABLE Performances (
        ...
        LastUpdateTime  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
        ...
    ) PRIMARY KEY (...);
    
  2. Se você estiver executando inserções ou atualizações com o DML, use a função PENDING_COMMIT_TIMESTAMP para gravar o carimbo de data/hora de confirmação.

    Se você estiver executando inserções ou atualizações com mutações, use a string de marcador de posição spanner.commit_timestamp() em inserções ou atualizações na coluna de carimbo de data/hora de confirmação. Também é possível usar a constante do carimbo de data/hora de confirmação fornecida pela biblioteca de cliente. Por exemplo, essa constante no cliente Java é Value.COMMIT_TIMESTAMP.

Quando o Spanner confirma a transação usando esses marcadores como valores de coluna, o carimbo de data/hora de confirmação real é gravado na coluna especificada (por exemplo: a coluna LastUpdateTime). Você pode usar esse valor de coluna para criar um histórico de atualizações na tabela.

Os valores de carimbo de data/hora de commit não têm garantia de exclusividade. As transações gravadas em conjuntos de campos não sobrepostos podem ter o mesmo carimbo de data/hora. As transações gravadas em conjuntos de campos sobrepostos têm carimbos de data/hora exclusivos.

Os carimbos de data/hora de confirmação do Spanner têm granularidade de microssegundos e são convertidos em nanossegundos quando armazenados em colunas TIMESTAMP.

Criar e excluir uma coluna de carimbo de data/hora de confirmação

Use a opção de coluna allow_commit_timestamp para adicionar e remover compatibilidade com carimbos de data/hora de commit:

  • Ao criar uma nova tabela para especificar que uma coluna é compatível com carimbos de data/hora de commit.
  • Ao alterar uma tabela existente:
    • para adicionar uma nova coluna compatível com carimbos de data/hora de commit;
    • para alterar uma coluna TIMESTAMP existente para aceitar carimbos de data/hora de commit;
    • para alterar uma coluna TIMESTAMP existente e remover o suporte de carimbo de data/hora de confirmação.

Chaves e índices

Use uma coluna de carimbo de data/hora de confirmação como uma coluna de chave primária ou como uma coluna sem chave. As chaves primárias podem ser definidas como ASC ou DESC.

  • ASC (padrão): as chaves crescentes são ideais para responder a consultas de uma hora específica em diante.
  • DESC: as chaves decrescentes mantêm as linhas mais recentes no topo da tabela. Elas dão acesso rápido aos registros mais recentes.

A opção allow_commit_timestamp precisa ser consistente entre as chaves primárias das tabelas pai e filho. Se a opção não for consistente entre as chaves primárias, o Spanner retornará um erro. A única ocasião em que a opção pode ser inconsistente é quando você está criando ou atualizando o esquema.

O uso de carimbos de data/hora de confirmação nas situações a seguir cria pontos de acesso que reduzem o desempenho dos dados:

  • Coluna de carimbo de edata/hora de confirmação como a primeira parte da chave primária de uma tabela:

    CREATE TABLE Users (
      LastAccess TIMESTAMP NOT NULL,
      UserId     INT64 NOT NULL,
      ...
    ) PRIMARY KEY (LastAccess, UserId);
    
  • A primeira parte da chave primária de um índice secundário:

    CREATE INDEX UsersByLastAccess ON Users(LastAccess)
    

    ou

    CREATE INDEX UsersByLastAccessAndName ON Users(LastAccess, FirstName)
    

Pontos de acesso reduzem o desempenho dos dados, mesmo com baixas taxas de gravação. Quando os carimbos de data/hora de confirmação são ativados em colunas que não são chave e nem estão indexadas, não há sobrecarga de desempenho.

Criar uma coluna de carimbo de data/hora de confirmação

A DDL a seguir cria uma tabela com uma coluna compatível com carimbos de data/hora de 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

Adicionar a opção altera a coluna do carimbo de data/hora assim:

A opção allow_commit_timestamp diferencia maiúsculas e minúsculas.

Adicionar uma coluna de carimbo de data/hora de confirmação a uma tabela atual

Para adicionar uma coluna de carimbo de data/hora de confirmação a uma tabela existente, use a instrução ALTER TABLE. Por exemplo, para adicionar uma coluna LastUpdateTime à tabela Performances, use a seguinte instrução:

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

Converter uma coluna de carimbo de data/hora em uma coluna de carimbo de data/hora de confirmação

É possível converter uma coluna de carimbo de data/hora em uma coluna de carimbo de data/hora de confirmação, mas isso exige que o Spanner valide se os valores do carimbo de data/hora atuais estão no passado. Exemplo:

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

Não é possível alterar o tipo de dados nem a anotação NULL de uma coluna em uma instrução ALTER TABLE que inclui SET OPTIONS. Para mais detalhes, consulte Linguagem de definição de dados.

Remover a opção de carimbo de data/hora de confirmação

Se você quiser remover o suporte ao carimbo de data/hora de confirmação de uma coluna, use a opção allow_commit_timestamp=null em uma instrução ALTER TABLE. O comportamento do carimbo de data/hora de confirmação é removido, mas a coluna continua sendo um carimbo de data/hora. Mudar essa opção não altera outras características da coluna, como tipo ou nulidade (NOT NULL). Por exemplo:

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

Gravar um carimbo de data/hora de confirmação usando uma instrução DML

Use a função PENDING_COMMIT_TIMESTAMP para gravar o carimbo de data/hora de confirmação em uma instrução DML. O Spanner seleciona o carimbo de data/hora de confirmação quando a transação é confirmada.

A instrução DML a seguir atualiza a coluna LastUpdateTime na tabela Performances com o carimbo de data/hora de confirmação:

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

O exemplo de código a seguir usa a função PENDING_COMMIT_TIMESTAMP para gravar o carimbo de data/hora de confirmação na coluna 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"

Os carimbos de data/hora de confirmação só podem ser gravados em colunas anotadas com a opção allow_commit_timestamp=true.

Se você tiver mutações nas linhas de várias tabelas, precisará especificar spanner.commit_timestamp() (ou a constante da biblioteca de cliente) para a coluna de carimbo de data/hora de confirmação em cada tabela.

Consultar uma coluna de carimbo de data/hora de confirmação

O exemplo a seguir consulta a coluna do carimbo de data/hora de commit da tabela.

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

Forneça seu próprio valor para a coluna do carimbo de data/hora de confirmação

Forneça um valor próprio para a coluna do carimbo de data/hora de confirmação, em vez de transmitir spanner.commit_timestamp() (ou a constante da biblioteca de cliente) como o valor da coluna. O valor precisa ser um carimbo de data/hora no passado. Essa restrição garante que gravar carimbos de data/hora seja uma operação rápida e barata. O servidor retornará um erro FailedPrecondition se um carimbo de data/hora futuro for especificado.

Criar um log de mudanças

Suponhamos que você queira criar um registro de alterações de cada mutação feita em uma tabela e use esse registro na auditoria. Um exemplo seria uma tabela que armazenasse o histórico de alterações em documentos de processamento de texto. O carimbo de data/hora de commit facilita a criação do registro de alterações, porque os carimbos de data/hora podem impor a ordem das entradas do registro de alterações. Crie um registro de alterações que armazene o histórico de alterações em um determinado documento usando um esquema como o exemplo a seguir:

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;

Para criar um registro de alterações, insira uma nova linha em DocumentHistory na mesma transação em que você inserir ou atualizar uma linha em Document. Na inserção da nova linha em DocumentHistory, use o marcador spanner.commit_timestamp() (ou a constante da biblioteca de cliente) para dizer ao Spanner para gravar o carimbo de data/hora de confirmação na coluna Ts. A intercalação da tabela DocumentsHistory com a tabela Documents permitirá conhecer a localização dos dados, além de inserções e atualizações mais eficientes. No entanto, ela também adiciona a restrição de que as linhas pai e filho precisam ser excluídas juntas. Para manter as linhas em DocumentHistory depois que as linhas em Documents forem excluídas, não intercale as tabelas.

Otimizar consultas de dados recentes com carimbos de data/hora de confirmação

Os carimbos de data/hora de confirmação permitem uma otimização do Spanner que pode reduzir a E/S da consulta ao recuperar dados gravados após um determinado momento.

Para ativar essa otimização, a cláusula WHERE de uma consulta precisa incluir uma comparação entre a coluna de carimbo de data/hora de confirmação da tabela e um horário específico fornecido, com os seguintes atributos:

  • Forneça o horário específico como uma expressão constante: um literal, um parâmetro ou uma função com argumentos próprios que sejam avaliados como constantes.

  • Compare se o carimbo de data/hora de confirmação é mais recente do que o determinado usando os operadores > ou >=.

  • Opcionalmente, adicione mais restrições à cláusula WHERE com AND. Estender a cláusula com OR desqualifica a consulta dessa otimização.

Por exemplo, considere a tabela Performances a seguir, que inclui uma coluna de carimbo de data/hora de confirmação:

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

Essa consulta se beneficia da otimização do carimbo de data/hora de confirmação descrita anteriormente, porque ela tem uma comparação de maior ou igual a entre a coluna de carimbo de data/hora de confirmação da tabela e uma expressão constante. Neste caso, um literal:

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

A consulta a seguir também se qualifica para a otimização, já que tem uma comparação maior que entre o carimbo de data/hora de confirmação e uma função com argumentos todos avaliados como constantes durante a execução da consulta:

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

A seguir