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