本主题介绍如何为使用 Spanner 执行的每个插入和更新操作编写提交时间戳。
提交时间戳概览
基于 TrueTime 技术的提交时间戳是在数据库中提交事务的时间。您可以通过原子方式存储提交内容 事务时间戳插入一个列中。 借助存储在表中的提交时间戳,您可以确定变更的确切顺序并构建更改日志等功能。
要在数据库中插入提交时间戳,请完成以下步骤:
创建一个
SPANNER.COMMIT_TIMESTAMP
类型的列。例如:CREATE TABLE Performances ( ... LastUpdateTime SPANNER.COMMIT_TIMESTAMP NOT NULL, ... PRIMARY KEY (...) ) ;
如果您使用 DML 执行插入或更新操作, 使用
SPANNER.PENDING_COMMIT_TIMESTAMP()
函数编写提交 时间戳。如果使用准备好的语句执行插入或更新操作 或变更 使用占位符字符串
SPANNER.COMMIT_TIMESTAMP()
提交时间戳列。您还可以使用 客户端库提供的提交时间戳常量。例如, 常量为Value.COMMIT_TIMESTAMP
。
当 Spanner 使用这些占位符作为列值提交事务时,实际提交时间戳将写入指定的列。然后,您可以使用此列值来创建表的更新历史记录。
提交时间戳的值不保证是唯一的。写入不重叠字段集的事务可能具有相同的时间戳。写入重叠字段集的事务具有唯一的时间戳。
Spanner 提交时间戳以微秒为单位,当存储在 SPANNER.COMMIT_TIMESTAMP
列中时会转换为纳秒。
键和索引
您可以使用提交时间戳列作为主键列或非键列。主键可以定义为 ASC
或 DESC
。
ASC
(默认)- 升序键适用于解答从特定时间往前的查询。DESC
- 降序键将最新的行保留在表的顶部,可提供对最近记录的快速访问。
避开热点
在以下情况下使用提交时间戳 热点 降低数据性能:
提交时间戳列作为表的主键的第一部分。
CREATE TABLE Users ( LastAccess SPANNER.COMMIT_TIMESTAMP NOT NULL, UserId bigint NOT NULL, ... PRIMARY KEY (LastAccess, UserId) ) ;
将提交时间戳主键列用作二级索引的第一部分。
CREATE INDEX UsersByLastAccess ON Users(LastAccess)
或
CREATE INDEX UsersByLastAccessAndName ON Users(LastAccess, FirstName)
热点缩小 数据性能。如果在没有索引的非键列上启用提交时间戳,则不会产生任何性能开销。
将提交时间戳列添加到现有表中
要将提交时间戳列添加到现有表中,请使用 ALTER TABLE
语句。例如,要将 LastUpdateTime
列添加到 Performances
表中,请使用以下语句:
ALTER TABLE Performances ADD COLUMN LastUpdateTime SPANNER.COMMIT_TIMESTAMP;
使用 DML 语句写入提交时间戳
使用 SPANNER.PENDING_COMMIT_TIMESTAMP()
函数在 DML 语句中写入提交时间戳。Spanner 在事务时选择提交时间戳
提交。
以下 DML 语句使用提交时间戳更新 Performances
表中的 LastUpdateTime
列:
UPDATE Performances SET LastUpdateTime = SPANNER.PENDING_COMMIT_TIMESTAMP()
WHERE SingerId=1 AND VenueId=2 AND EventDate="2015-10-21"
使用变更插入行
插入行时,只有将列包含在列列表中并传递 spanner.commit_timestamp()
占位符字符串(或客户端库常量)作为其值时,Spanner 才会写入提交时间戳值。例如:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
如果在多个表的行中存在变更,则必须为每个表的提交时间戳列指定 spanner.commit_timestamp()
(或客户端库常量)。
使用变更更新行
更新行时,只有将列包含在列列表中并传递 spanner.commit_timestamp()
占位符字符串(或客户端库常量)作为其值时,Spanner 才会写入提交时间戳值。您无法更新某一行的主键。要更新主键,请删除现有行并创建一个新行。
例如,要更新名为 LastUpdateTime
的提交时间戳列,请执行以下操作:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
如果在多个表的行中存在变更,则必须为每个表的提交时间戳列指定 spanner.commit_timestamp()
(或客户端库常量)。
查询提交时间戳列
以下示例展示了如何查询表的提交时间戳列。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
为提交时间戳列提供您自己的值
在代码中,您可以为提交时间戳列提供自己的值
而不是传递 spanner.commit_timestamp()
(或可用的客户端库)
常量)作为
列值。该值必须为过去的时间戳。此限制可确保写入时间戳的操作成本低且速度快。确认某个值为过去的时间戳的一种简单方法是将其与 CURRENT_TIMESTAMP
SQL 函数的返回值进行比较。如果指定了未来的时间戳,服务器将返回 FailedPrecondition
错误。
创建更新日志
假设您希望创建一个关于发生在表中的每个变更的更改日志,然后使用该更改日志进行审核。例如一个用于存储字处理文档的更改记录的表。提交时间戳使得创建更改日志更加容易,因为时间戳可以强制对更改日志条目进行排序。您可以使用类似以下示例的架构构建一个更改日志,以便将更改记录存储到指定的文档中:
CREATE TABLE Documents (
UserId int8 NOT NULL,
DocumentId int8 NOT NULL,
Contents text NOT NULL,
PRIMARY KEY (UserId, DocumentId)
);
CREATE TABLE DocumentHistory (
UserId int8 NOT NULL,
DocumentId int8 NOT NULL,
Ts SPANNER.COMMIT_TIMESTAMP NOT NULL,
Delta text,
PRIMARY KEY (UserId, DocumentId, Ts)
) INTERLEAVE IN PARENT Documents;
要创建一个更新日志,请在为 Document
插入或更新某一行的相同事务中为 DocumentHistory
插入一个新行。在为 DocumentHistory
插入新行时,请使用占位符 spanner.commit_timestamp()
(或客户端库常量),以告诉 Spanner 将提交时间戳写入列 Ts
。将 DocumentsHistory
表与 Documents
表交错将实现数据局部性及更高效的插入和更新。但是,它也存在必须一起删除父行和子行的限制。若要在 DocumentHistory
中的行删除后保留 Documents
中的行,请不要交错表。
使用提交时间戳优化近期数据查询
提交时间戳可启用一种 Spanner 优化,该优化可在检索特定时间之后写入的数据时减少查询 I/O。
要激活此优化,查询的 WHERE
子句必须包含
表的提交时间戳列与特定时间之间的比较
属性,其中包含以下属性:
将具体时间提供为常量表达式:字面量、参数或自身参数求值为常量的函数。
通过
>
或>=
运算符比较提交时间戳是否晚于给定时间。(可选)使用
AND
为WHERE
子句添加进一步的限制。 如果使用OR
扩展子句,则会导致查询不符合此条件 优化。
例如,假设有以下 Performances
表,其中包含一个提交时间戳列:
CREATE TABLE Performances (
SingerId bigint NOT NULL,
VenueId bigint NOT NULL,
EventDate timestamp with time zone NOT NULL,
Revenue bigint,
LastUpdateTime spanner.commit_timestamp,
PRIMARY KEY(SingerId, VenueId, EventDate)
);
此查询受益于前面介绍的提交时间戳优化,因为它会在表的提交时间戳列与常量表达式(在本例中为字面量)之间进行大于或等于比较:
SELECT * FROM Performances WHERE LastUpdateTime >= '2022-01-01';