Spanner 允许您在不停机的情况下进行架构更新。您可以通过几种方式更新现有数据库的架构:
在 Google Cloud Console 中
在 Spanner Studio 页面上提交
ALTER TABLE
命令。如需访问 Spanner Studio 页面,请在“数据库概览”或“表概览”页面中点击 Spanner Studio。
使用
gcloud spanner
命令行工具使用
gcloud spanner databases ddl update
命令提交ALTER TABLE
命令。使用客户端库
使用
projects.instances.databases.updateDdl
REST API使用
UpdateDatabaseDdl
RPC API
支持的架构更新
Spanner 支持对现有数据库架构执行以下更新操作:
- 添加或删除已命名的架构。
- 新建一个表。 新表格中的列可以为
NOT NULL
。 - 删除一个表,前提是该表内没有交错其他表,并且没有二级索引。
- 创建或删除带有外键的表。
- 在现有表中添加或移除外键。
- 将一个非键列添加到任何表。新的非键列不能为
NOT NULL
。 - 从任何表中删除非键列,除非二级索引、外键、存储的生成列或检查限制条件使用该列。
- 将
NOT NULL
添加到非键列,从而排除ARRAY
列。 - 从非键列中移除
NOT NULL
。 - 将
STRING
列更改为BYTES
列,或将BYTES
列更改为STRING
列。 - 将
PROTO
列更改为BYTES
列,或将BYTES
列更改为PROTO
列。 - 更改
PROTO
列的 proto 消息类型。 - 将新值添加到
ENUM
定义中,并重命名现有值:ALTER PROTO BUNDLE
。 - 在提供的情况下,以任意方式更改
PROTO BUNDLE
中定义的消息 这些消息中经过修改的字段不会用作任何表中的键 以及现有数据是否满足新的限制条件。 - 增加或减少
STRING
或BYTES
类型的长度限制(包括增加到MAX
),前提是它不是由一个或多个子表继承的主键列。 - 增加或减少
ARRAY<STRING>
、ARRAY<BYTES>
、 或ARRAY<PROTO>
列。 - 在值和主键列中启用或停用提交时间戳。
- 添加或移除二级索引。
- 在现有表中添加或移除检查限制条件。
- 在现有表中添加或移除存储的生成列。
- 构造一个新的优化工具统计信息软件包。
- 创建和管理视图。
- 创建和管理序列。
- 创建数据库角色并授予权限。
- 设置、更改或删除列的默认值。
- 更改数据库选项(例如
default_leader
或version_retention_period
)。 - 创建和管理变更数据流。
- 创建和管理机器学习模型。
不支持的架构更新
Spanner 不支持现有资源的以下架构更新 数据库:
- 如果表或索引键引用了
ENUM
类型的PROTO
字段,则无法从 proto 枚举中移除ENUM
值。(支持从ENUM<>
列使用的枚举中移除ENUM
值,包括在这些列用作键时。)
架构更新性能
Spanner 中的架构更新不需要停机。如果您向某个 Spanner 数据库发出一批 DDL 语句,则当 Spanner 以长时间运行的操作形式应用更新时,您可以继续从数据库写入和读取数据,而不会发生中断。
执行 DDL 语句所需的时间取决于更新是否需要验证现有数据或回填任何数据。例如,如果您向现有列添加 NOT NULL
注解,则 Spanner 必须读取列中的所有值,以确保该列不包含任何 NULL
值。如果有大量数据需要验证,则此步骤可能需要很长时间。另一个示例是,如果您向数据库添加索引:Spanner 会使用现有数据回填索引,并且该过程可能需要很长时间,具体取决于索引的定义和相应基表的大小。不过,如果您在表格中添加新列
没有现有数据需要验证,因此 Spanner 可以进一步
。
总之,不需要 Spanner 验证现有数据的架构更新可以在几分钟内完成。需要验证的架构更新可能需要更长时间,具体取决于需要验证的现有数据量,但数据验证操作会在后台以低于生产流量的优先级执行。下一节将详细讨论需要验证数据的架构更新。
根据视图定义验证的架构更新
进行架构更新时,Spanner 会验证更新是否不会使用于定义现有视图的查询无效。如果验证成功,则架构更新就会成功。如果验证不成功,则架构更新将失败。如需了解详情,请参阅创建视图时的最佳做法。
需要验证数据的架构更新
您可以执行需要验证现有数据是否符合新的限制条件的架构更新。当架构更新需要验证数据时,Spanner 不允许对受影响的架构实体进行冲突的架构更新,并在后台验证数据。如果验证成功,则架构更新就会成功。如果验证失败,则架构更新不成功。验证操作会作为长时间运行的操作执行。您可以检查这些操作的状态,以此确定这些操作是成功还是失败。
例如,假设您使用music.proto
RecordLabel
枚举和 Songwriter
协议消息:
enum RecordLabel {
COOL_MUSIC_INC = 0;
PACIFIC_ENTERTAINMENT = 1;
XYZ_RECORDS = 2;
}
message Songwriter {
required string nationality = 1;
optional int64 year_of_birth = 2;
}
如需在架构中添加 Songwriters
表,请执行以下操作:
GoogleSQL
CREATE PROTO BUNDLE (
googlesql.example.music.Songwriter,
googlesql.example.music.RecordLabel,
);
CREATE TABLE Songwriters (
Id INT64 NOT NULL,
FirstName STRING(1024),
LastName STRING(1024),
Nickname STRING(MAX),
OpaqueData BYTES(MAX),
SongWriter googlesql.example.music.Songwriter
) PRIMARY KEY (Id);
CREATE TABLE Albums (
SongwriterId INT64 NOT NULL,
AlbumId INT64 NOT NULL,
AlbumTitle STRING(MAX),
Label INT32
) PRIMARY KEY (SongwriterId, AlbumId);
可进行以下架构更新,但这些架构更新需要验证,并且可能需要较长时间才能完成,具体取决于现有数据量:
将
NOT NULL
注释添加到非键列。例如:ALTER TABLE Songwriters ALTER COLUMN Nickname STRING(MAX) NOT NULL;
缩短列的长度。例如:
ALTER TABLE Songwriters ALTER COLUMN FirstName STRING(10);
从
BYTES
改为STRING
。例如:ALTER TABLE Songwriters ALTER COLUMN OpaqueData STRING(MAX);
从
INT64/INT32
改为ENUM
。例如:ALTER TABLE Albums ALTER COLUMN Label googlesql.example.music.RecordLabel;
从
RecordLabel
枚举定义中移除现有值。在现有
TIMESTAMP
列上启用提交时间戳。例如:ALTER TABLE Albums ALTER COLUMN LastUpdateTime SET OPTIONS (allow_commit_timestamp = true);
向现有表添加检查限制条件。
将存储的生成列添加到现有表中。
创建带有外键的新表。
向现有表中添加外键。
如果基础数据不满足新的限制条件,则这些架构更新会失败。例如,如果 Nickname
列中的任何值为 NULL
,则 ALTER TABLE Songwriters ALTER COLUMN Nickname
STRING(MAX) NOT NULL
语句会失败,因为现有数据不符合新定义的 NOT NULL
限制。
验证数据操作可能需要数分钟到数小时。完成数据验证的时间取决于以下因素:
- 数据集的大小
- 实例的计算容量
- 实例上的负载
在更新完成之前,某些架构更新可以更改数据库请求的行为。例如,如果在某列中添加 NOT NULL
,对于那些要对该列使用 NULL
的新请求,Spanner 会几乎立即开始拒绝写入。如果新的架构更新因未通过数据验证而最终失败,那么写入操作会在一段时间内被阻止,即使这些操作原本应该会被旧架构接受,也是如此。
您可以使用 projects.instances.databases.operations.cancel
方法或使用 gcloud spanner operations
取消长时间运行的数据验证操作。
批量执行语句的顺序
如果使用 Google Cloud CLI、REST API 或 RPC API,您可以发出一批(一个或多个)CREATE
、ALTER
或 DROP
语句。
Spanner 将按顺序应用同一批次中的语句,并在第一个错误处停止。如果应用语句时产生错误,则系统会回滚该语句。该批次中以前应用过的语句的结果不会回滚。
Spanner 可能会对不同批次的语句进行组合和重新排序,
可能会将不同批次的语句混合为一项原子更改,
会应用于数据库在每个原子更改中,不同批次的语句按照任意顺序执行。例如,如果一批语句包含 ALTER TABLE MyTable ALTER COLUMN MyColumn STRING(50)
,而另一批语句包含 ALTER TABLE MyTable ALTER COLUMN MyColumn
STRING(20)
,则 Spanner 将会使该列处于这两个状态中的某个状态,但不具体指定哪一个状态。
在架构更新期间创建的架构版本
Spanner 使用架构版本控制,以便在 大型数据库的架构更新。Spanner 维护着旧版架构 版本以支持在处理架构更新时进行读取。然后,Spanner 会创建一个或多个新版本的架构来处理架构更新。每个版本包含单个原子更改中的语句集合的结果。
架构版本不一定与批量 DDL 语句或单独的 DDL 语句一一对应。某些单独的 DDL 语句(例如为现有基表或需要数据验证的语句创建索引)会导致产生多个架构版本。在其他情况下,可以在单个版本中将多个 DDL 语句作为一批进行处理。旧架构版本可能会占用大量服务器和存储资源,并且会一直保留,直到过期(不再需要为旧版本数据的读取提供服务)。
下表显示了 Spanner 更新架构所需的时间。
架构操作 | 估计用时 |
---|---|
CREATE TABLE |
几分钟 |
CREATE INDEX |
如果基表是在索引之前创建的,则为几分钟到几个小时。 如果语句与基表的 |
DROP TABLE |
几分钟 |
DROP INDEX |
几分钟 |
ALTER TABLE ... ADD COLUMN |
几分钟 |
ALTER TABLE ... ALTER COLUMN |
如果需要后台验证,则为几个小时。 如果不需要后台验证,则为几分钟。 |
ALTER TABLE ... DROP COLUMN |
分钟 |
ANALYZE |
根据数据库大小,从几分钟到几小时不等。 |
数据类型更改和更改流
如果更改某列的数据类型,
stream 观看,column_types
字段
相关的后续更改
记录
和 old_values
中的 JSON 数据
条记录的mods
字段。
变更数据流记录的 mods
字段的 new_values
始终与列的当前类型匹配。更改监控列的数据类型不会
会影响早于该更改的任何变更数据流记录。
在从 BYTES
更改为 STRING
这一具体情况下,
Spanner 验证列的旧列
值。
因此,Spanner 已安全地解码
BYTES
类型的值转换为字符串。
变更数据流记录。
架构更新的最佳做法
以下各节介绍了更新架构的最佳做法。
发出架构更新之前的过程
在您发出架构更新之前,请完成以下操作:
验证数据库中您正在更改的所有现有数据是否满足架构更新所施加的限制条件。由于某些类型的架构更新的成功取决于数据库中的数据,而不仅仅取决于其当前架构,因此,测试数据库的架构成功更新并不能保证生产数据库的架构可成功更新。以下是一些常见示例:
- 如果您向现有列添加
NOT NULL
注释,则需要确认该列不包含任何现有的NULL
值。 - 如果您要缩短
STRING
或BYTES
列允许的长度, 检查该列中的所有现有值是否符合长度要求 限制条件。
- 如果您向现有列添加
如果要向正在进行架构更新的列、表或索引中写入数据,请确保所写入的值符合新的限制条件。
如果要删除某个列、表或索引,请确保不再向其中写入数据或从中读取数据。
限制架构更新的频率
如果您在短时间内执行太多架构更新,Spanner
可以throttle
已排队的架构更新的处理。这是因为 Spanner 限制了
用于存储架构版本的空间。如果保留期限内有太多旧架构版本,您的架构更新可能会受到节流。架构更改的最大速率取决于多种因素,其中之一是数据库中的列总数。例如,如果数据库包含 2,000 个列(在 INFORMATION_SCHEMA.COLUMNS
中大约为 2,000 行),则在保留期内最多可以执行 1,500 次架构更改(如果架构更改需要多个版本,则更改次数会更少)。要查看
持续进行架构更新,请使用
gcloud spanner operations list
命令并按 DATABASE_UPDATE_DDL
类型的操作进行过滤。要取消
持续更新架构,请使用
gcloud spanner operations cancel
命令并指定操作 ID。
DDL 语句的批处理方式及其在每个批次中的顺序, 会影响生成的架构版本数量。为了最大限度地 可在任意给定时间段内执行的新架构更新,则应使用 最大限度地减少架构版本数量。大型更新中介绍了一些经验法则。
如架构版本中所述,某些 DDL 语句将创建多个架构版本,当考虑进行批处理以及每个批次内的排序时,这些版本非常重要。有两种主要类型的语句可能会创建多个架构版本:
- 可能需要回填索引数据的语句,例如
CREATE INDEX
- 可能需要验证现有数据的语句,例如添加
NOT NULL
但是,这些类型的语句不一定会创建多个架构版本。Spanner 将尝试检测这些类型的语句何时
对其进行优化,以避免使用多个架构版本(因为架构版本依赖于批处理)。
例如,如果 CREATE INDEX
语句与索引基表的 CREATE TABLE
语句在同一批处理中出现,并且没有任何针对其他表的插入语句,则可以避免回填索引数据,因为 Spanner 可以保证在创建索引时基表为空。大型更新部分介绍了如何使用此属性高效地创建多个索引。
如果无法批量处理 DDL 语句以避免创建多个架构版本,您应限制单个数据库的架构在其保留期限内的架构更新次数。增加创建架构的时间范围 更新可让 Spanner 在发布新架构之前 版本。
- 对于某些关系型数据库管理系统,有一些软件包会在每次生产部署时对数据库进行一连串的升级和降级架构更新。Spanner 不建议使用这些类型的处理。
- Spanner 经过优化,可使用主键对数据进行分区, 多租户解决方案。 如果多租户解决方案对每个客户使用单独表, 需要很长时间才能执行的 。
- 需要验证或索引回填的架构更新会使用更多服务器资源,因为每个语句都会在内部创建多个架构版本。
大型架构更新方法
要创建表并为该表创建大量索引,最好的方法是 可以同时创建所有这些架构 已创建版本。最佳做法是在创建索引之后立即 DDL 语句列表中的表。您可以在创建数据库时创建表及其索引,也可以在单个大批量语句中创建表及其索引。如果您需要创建多个表,并且每个表都有多个索引,则可以将所有语句包含在一个批处理中。如果所有语句都可以使用单个架构版本一起执行,您可以在单个批处理中包含数千个语句。
当语句需要回填索引数据或需要执行数据验证时,它无法在单个架构版本中执行。当索引的基表已存在(原因可能是在上一批次的 DDL 语句中创建了基表,或者是需要多个架构版本的 CREATE TABLE
和 CREATE INDEX
语句之间的批次中有一个语句)时,CREATE INDEX
语句便会发生上述问题。Spanner 要求每个批次中的此类语句不超过 10 个。创建需要回填的索引时,请特别注意,每个索引需要使用多个架构版本,因此最好每天创建不超过 3 个需要回填的新索引(无论以何种方式进行批处理,除非这种批处理方式能够避免回填)。
例如,这批语句将使用单个架构版本:
GoogleSQL
CREATE TABLE Singers ( SingerId INT64 NOT NULL, FirstName STRING(1024), LastName STRING(1024), ) PRIMARY KEY (SingerId); CREATE INDEX SingersByFirstName ON Singers(FirstName); CREATE INDEX SingersByLastName ON Singers(LastName); CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), ) PRIMARY KEY (SingerId, AlbumId); CREATE INDEX AlbumsByTitle ON Albums(AlbumTitle);
相比之下,该批次将使用许多架构版本,因为 UnrelatedIndex
需要回填(因为其基表必须已存在),并且强制要求以下所有索引也需要进行回填(即使它们与其基表位于同一批次):
GoogleSQL
CREATE TABLE Singers ( SingerId INT64 NOT NULL, FirstName STRING(1024), LastName STRING(1024), ) PRIMARY KEY (SingerId); CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), ) PRIMARY KEY (SingerId, AlbumId); CREATE INDEX UnrelatedIndex ON UnrelatedTable(UnrelatedIndexKey); CREATE INDEX SingersByFirstName ON Singers(FirstName); CREATE INDEX SingersByLastName ON Singers(LastName); CREATE INDEX AlbumsByTitle ON Albums(AlbumTitle);
最好是将 UnrelatedIndex
的创建操作移到批次的末尾或移到其他批次,以最大限度地减少架构版本。
等待 API 请求完成
在发出 projects.instances.databases.updateDdl
(REST API) 或 UpdateDatabaseDdl
(RPC API) 请求时,请分别使用 projects.instances.databases.operations.get
(REST API) 或 GetOperation
(RPC API) 等待每个请求完成,然后再开始新的请求。等待每个请求完成后,您的应用就可以跟踪架构更新的进度。这样做还可将待处理架构更新的积压量保持在可管理的范围内。
批量加载
如果您在创建表后将数据批量加载到表中,则在加载数据后创建索引通常更高效。如果要添加多个索引,则更高效的做法可能是在创建数据库时在初始架构中包含所有表和索引,如大型更新方法中所述。