Marcas de tiempo de confirmación en bases de datos con dialecto GoogleSQL

En este tema se describe cómo escribir una marca de tiempo de confirmación para cada operación de inserción y actualización que realices con Spanner. Para usar esta función, activa la opción allow_commit_timestamp en una columna TIMESTAMP y, a continuación, escribe la marca de tiempo como parte de cada transacción.

Información general

La marca de tiempo de confirmación, basada en la tecnología TrueTime, es el momento en el que se confirma una transacción en la base de datos. La opción de columna allow_commit_timestamp le permite almacenar de forma atómica la marca de tiempo de la confirmación en una columna. Con las marcas de tiempo de las confirmaciones almacenadas en las tablas, puedes determinar el orden exacto de las mutaciones y crear funciones como los registros de cambios.

Para insertar marcas de tiempo de confirmación en tu base de datos, sigue estos pasos:

  1. Crea una columna de tipo TIMESTAMP con la opción de columna allow_commit_timestamp definida como true en la definición del esquema. Por ejemplo:

    CREATE TABLE Performances (
        ...
        LastUpdateTime  TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
        ...
    ) PRIMARY KEY (...);
    
  2. Si insertas o actualizas datos con DML, usa la función PENDING_COMMIT_TIMESTAMP para escribir la marca de tiempo de la confirmación.

    Si realiza inserciones o actualizaciones con mutaciones, use la cadena de marcador de posición spanner.commit_timestamp() en las inserciones o actualizaciones de su columna timestamp de confirmación. También puedes usar la constante de marca de tiempo de confirmación proporcionada por la biblioteca de cliente. Por ejemplo, esta constante del cliente de Java es Value.COMMIT_TIMESTAMP.

Cuando Spanner confirma la transacción usando estos marcadores de posición como valores de columna, la marca de tiempo de confirmación real se escribe en la columna especificada (por ejemplo, la columna LastUpdateTime). Después, podría usar este valor de columna para crear un historial de actualizaciones de la tabla.

No se garantiza que los valores de la marca de tiempo de la confirmación sean únicos. Las transacciones que escriben en conjuntos de campos que no se solapan pueden tener la misma marca de tiempo. Las transacciones que escriben en conjuntos de campos superpuestos tienen marcas de tiempo únicas.

Las marcas de tiempo de confirmación de Spanner tienen una granularidad de microsegundos y se convierten a nanosegundos cuando se almacenan en columnas TIMESTAMP.

Crear y eliminar una columna de marca de tiempo de confirmación

Usa la opción de columna allow_commit_timestamp para añadir y quitar la compatibilidad con marcas de tiempo de confirmación:

  • Cuando crees una tabla para especificar que una columna admite marcas de tiempo de confirmación.
  • Cuando modifiques una tabla:
    • para añadir una nueva columna que admita marcas de tiempo de confirmación,
    • para modificar una columna TIMESTAMP para que admita marcas de tiempo de confirmación,
    • para modificar una columna TIMESTAMP para eliminar la compatibilidad con la marca de tiempo de confirmación

Claves e índices

Puede usar una columna de marca de tiempo de confirmación como columna de clave principal o como columna que no sea clave. Las claves principales se pueden definir como ASC o DESC.

  • ASC (predeterminado): las claves ascendentes son ideales para responder a consultas desde un momento específico en adelante.
  • DESC: las claves descendentes mantienen las filas más recientes en la parte superior de la tabla. Proporcionan acceso rápido a los registros más recientes.

La opción allow_commit_timestamp debe ser coherente en todas las claves principales de las tablas principales y secundarias. Si la opción no es coherente en todas las claves principales, Spanner devuelve un error. La única vez que la opción puede ser incoherente es cuando creas o actualizas el esquema.

Si se usan marcas de tiempo de confirmación en los siguientes casos, se crearán puntos de acceso que reducirán el rendimiento de los datos:

  • Columna de marca de tiempo de confirmación como primera parte de la clave principal de una tabla:

    CREATE TABLE Users (
      LastAccess TIMESTAMP NOT NULL,
      UserId     INT64 NOT NULL,
      ...
    ) PRIMARY KEY (LastAccess, UserId);
    
  • La primera parte de la clave principal de un índice secundario:

    CREATE INDEX UsersByLastAccess ON Users(LastAccess)
    

    o

    CREATE INDEX UsersByLastAccessAndName ON Users(LastAccess, FirstName)
    

Los puntos de acceso reducen el rendimiento de los datos, incluso con tasas de escritura bajas. No hay sobrecarga de rendimiento si las marcas de tiempo de confirmación están habilitadas en columnas que no son de clave y que no están indexadas.

Crear una columna de marca de tiempo de confirmación

El siguiente DDL crea una tabla con una columna que admite marcas de tiempo de confirmación.

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

Si añades esta opción, la columna de marca de tiempo cambiará de la siguiente manera:

  • Puedes usar la cadena de marcador de posición spanner.commit_timestamp() (o una constante proporcionada por la biblioteca de cliente) para las inserciones y las actualizaciones.
  • La columna solo puede contener valores anteriores. Para obtener más información, consulta Proporcionar tu propio valor para la marca de tiempo.

La opción allow_commit_timestamp distingue entre mayúsculas y minúsculas.

Añadir una columna de marca de tiempo de confirmación a una tabla

Para añadir una columna de marca de tiempo de confirmación a una tabla, usa la instrucción ALTER TABLE. Por ejemplo, para añadir una columna LastUpdateTime a la tabla Performances , usa la siguiente instrucción:

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

Convertir una columna de marca de tiempo en una columna de marca de tiempo de confirmación

Puedes convertir una columna de marca de tiempo en una columna de marca de tiempo de confirmación, pero para ello Spanner debe validar que los valores de marca de tiempo sean del pasado. Por ejemplo:

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

No puedes cambiar el tipo de datos ni la anotación NULL de una columna en una instrucción ALTER TABLE que incluya SET OPTIONS. Para obtener más información, consulta Lenguaje de definición de datos.

Quitar la opción de marca de tiempo de confirmación

Si quieres quitar la compatibilidad con la marca de tiempo de confirmación de una columna, usa la opción allow_commit_timestamp=null en una instrucción ALTER TABLE. Se ha eliminado el comportamiento de la marca de tiempo de confirmación, pero la columna sigue siendo una marca de tiempo. Cambiar la opción no modifica ninguna otra característica de la columna, como el tipo o la posibilidad de que sea nula (NOT NULL). Por ejemplo:

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

Escribir una marca de tiempo de confirmación con una instrucción DML

Usa la función PENDING_COMMIT_TIMESTAMP para escribir la marca de tiempo de confirmación en una instrucción DML. Spanner selecciona la marca de tiempo de confirmación cuando se confirma la transacción.

La siguiente instrucción DML actualiza la columna LastUpdateTime de la tabla Performances con la marca de tiempo de confirmación:

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

En el siguiente ejemplo de código se usa la función PENDING_COMMIT_TIMESTAMP para escribir la marca de tiempo de la confirmación en la columna 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"

Las marcas de tiempo de confirmación solo se pueden escribir en columnas anotadas con la opción allow_commit_timestamp=true.

Si tienes mutaciones en filas de varias tablas, debes especificar spanner.commit_timestamp() (o la constante de la biblioteca cliente) en la columna de marca de tiempo de confirmación de cada tabla.

Consultar una columna de marca de tiempo de confirmación

En el siguiente ejemplo se consulta la columna de marca de tiempo de confirmación de la tabla.

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

Proporcionar su propio valor para la columna de marca de tiempo de confirmación

Puede proporcionar su propio valor para la columna de marca de tiempo de la confirmación en lugar de enviar spanner.commit_timestamp() (o una constante de la biblioteca de cliente) como valor de la columna. El valor debe ser una marca de tiempo anterior a la fecha actual. Esta restricción asegura que escribir marcas de tiempo sea una operación rápida y económica. El servidor devuelve un error FailedPrecondition si se especifica una marca de tiempo futura.

Crear un registro de cambios

Supongamos que quieres crear un registro de cambios de cada mutación que se produzca en una tabla y, a continuación, usar ese registro de cambios para realizar auditorías. Por ejemplo, una tabla que almacene el historial de cambios de los documentos de procesamiento de textos. La marca de tiempo de la confirmación facilita la creación del registro de cambios, ya que las marcas de tiempo pueden imponer el orden de las entradas del registro de cambios. Puedes crear un registro de cambios que almacene el historial de cambios de un documento determinado con un esquema como el del siguiente ejemplo:

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 crear un registro de cambios, inserte una fila en DocumentHistory en la misma transacción en la que inserte o actualice una fila en Document. En la inserción de la nueva fila en DocumentHistory, use el marcador de posición spanner.commit_timestamp() (o la constante de la biblioteca cliente) para indicar a Spanner que escriba la marca de tiempo de la confirmación en la columna Ts. Entrelazar la tabla DocumentsHistory con la tabla Documents permitirá que los datos se ubiquen en el mismo lugar y que las inserciones y las actualizaciones sean más eficientes. Sin embargo, también añade la restricción de que las filas de la tabla principal y la secundaria se deben eliminar juntas. Para conservar las filas de DocumentHistory después de eliminar las filas de Documents, no entrelaces las tablas.

Optimizar las consultas de datos recientes con marcas de tiempo de confirmación

Las marcas de tiempo de confirmación permiten una optimización de Spanner que puede reducir la E/S de las consultas al recuperar datos escritos después de una hora concreta.

Para activar esta optimización, la cláusula WHERE de una consulta debe incluir una comparación entre la columna de marca de tiempo de confirmación de una tabla y una hora específica que proporciones, con los siguientes atributos:

  • Indica la hora específica como una expresión constante: un literal, un parámetro o una función cuyos argumentos se evalúen como constantes.

  • Compara si la marca de tiempo de la confirmación es más reciente que la hora indicada mediante los operadores > o >=.

  • También puede añadir más restricciones a la cláusula WHERE con AND. Si se amplía la cláusula con OR, la consulta no podrá beneficiarse de esta optimización.

Por ejemplo, considere la siguiente tabla Performances, que incluye una columna de marca de tiempo de confirmación:

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

Esta consulta se beneficia de la optimización de la marca de tiempo de confirmación descrita anteriormente, ya que incluye una comparación mayor o igual que entre la columna de marca de tiempo de confirmación de la tabla y una expresión constante (en este caso, un literal):

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

La siguiente consulta también cumple los requisitos para la optimización, ya que incluye una comparación "mayor que" entre la marca de tiempo de la confirmación y una función cuyos argumentos se evalúan como constantes durante la ejecución de la consulta:

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

Siguientes pasos