进行架构更新

Spanner 可让您在不停机的情况下进行架构更新。您可以更新 以多种方式更改现有数据库的架构:

支持的架构更新

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 中定义的消息 这些消息中经过修改的字段不会用作任何表中的键 并且现有数据满足新的限制条件。
  • 增加或减少 STRINGBYTES 类型的长度限制(包括增加到 MAX),前提是它不是由一个或多个子表继承的主键列。
  • 增加或减少 ARRAY<STRING>ARRAY<BYTES>、 或ARRAY<PROTO>列。
  • 在值和主键列中启用或停用提交时间戳
  • 添加或移除二级索引。
  • 在现有表中添加或移除检查限制条件。
  • 在现有表中添加或移除存储的生成列。
  • 构造一个新的优化工具统计信息软件包
  • 创建和管理视图
  • 创建和管理序列
  • 创建数据库角色并授予权限。
  • 设置、更改或删除列的默认值。
  • 更改数据库选项(例如 default_leaderversion_retention_period)。
  • 创建和管理变更数据流
  • 创建和管理机器学习模型。

不受支持的架构更新

Spanner 不支持现有资源的以下架构更新 数据库:

  • 如果存在由PROTOENUM 表或索引键,您不能从 proto 中移除 ENUM 值 枚举。(从 ENUM<> 使用的枚举中移除了 ENUM 值 列,包括将这些列用作键的情况。)

架构更新性能

Spanner 中的架构更新不需要停机。当您 批量 DDL 语句写入 Spanner 数据库,则可以继续写入 在应用 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 中的任何值,ALTER TABLE Songwriters ALTER COLUMN Nickname STRING(MAX) NOT NULL 语句就会失败 列为 NULL,因为现有数据不符合 NOT NULL 新定义的限制条件

验证数据操作可能需要数分钟到数小时。完成数据验证的时间取决于以下因素:

  • 数据集的大小
  • 实例的计算容量
  • 实例上的负载

在更新完成之前,某些架构更新可以更改数据库请求的行为。例如,如果您要将 NOT NULL 添加到 Spanner 几乎会立即开始拒绝新数据写入 对列使用 NULL 的请求。如果新的架构更新因未通过数据验证而最终失败,那么写入操作会在一段时间内被阻止,即使这些操作原本应该会被旧架构接受,也是如此。

您可以使用 projects.instances.databases.operations.cancel 方法或使用 gcloud spanner operations 取消长时间运行的数据验证操作。

批量执行语句的顺序

如果您使用 Google Cloud CLI、REST API 或 RPC API, 一个或多个 CREATEALTERDROP 语句的组合。

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

如果基表是在索引之前创建的,则为几分钟到几个小时。

如果语句与基表的 CREATE TABLE 语句同时执行,则为几分钟。

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 值。
    • 如果您要缩短 STRINGBYTES 列允许的长度, 检查该列中的所有现有值是否符合长度要求 限制条件。
  • 如果要向正在进行架构更新的列、表或索引中写入数据,请确保所写入的值符合新的限制条件。

  • 如果要删除某个列、表或索引,请确保不再向其中写入数据或从中读取数据。

限制架构更新的频率

如果您在短时间内执行太多架构更新,Spanner 可以throttle 已排队的架构更新的处理。这是因为 Spanner 限制了 用于存储架构版本的空间。您的架构更新可能受到限制 如果保留期限内的旧架构版本过多。通过 架构更改的最大速率取决于很多因素, 其中一个是数据库中的列总数。例如, 具有 2000 列(大约 2000 行, INFORMATION_SCHEMA.COLUMNS) 最多可以执行 1500 次架构更改(如果架构 (更改需要多个版本)。要查看 持续进行架构更新,请使用 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 语句列表中的表。您可以创建表及其 创建索引,也可以在单个大批量语句中使用。 如果您需要创建许多表,每个表都具有多个索引,则可以包含所有 在单个批次中处理语句。您可以在 如果可以使用一个单个批次中的所有语句同时执行 schema 版本。

当语句需要回填索引数据或需要执行数据验证时,它无法在单个架构版本中执行。当索引的基表已存在(原因可能是在上一批次的 DDL 语句中创建了基表,或者是需要多个架构版本的 CREATE TABLECREATE 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) 等待每个请求完成,然后再开始新的请求。等待每个请求完成后,您的应用就可以跟踪架构更新的进度。这样做还可将待处理架构更新的积压量保持在可管理的范围内。

批量加载

如果您在创建表后将数据批量加载到表中,则在加载数据后创建索引通常更高效。如果要添加多个索引,则更高效的做法可能是在创建数据库时在初始架构中包含所有表和索引,如大型更新方法中所述。