迁移主键

本文档介绍了如何将主键从源数据库表迁移到 Spanner。您必须熟悉主键迁移概览中提供的信息。

准备工作

  • 如需获得将主键迁移到 Spanner 所需的权限,请让您的管理员为您授予实例的 Cloud Spanner Database Admin (roles/spanner.databaseAdmin) IAM 角色。

迁移自动生成的顺序键

如果您要从使用顺序单调键的数据库迁移(例如 MySQL 中的 AUTO_INCREMENT、PostgreSQL 中的 SERIAL 或 SQL Server 或 Oracle 中的标准 IDENTITY 类型),请考虑以下概要迁移策略:

  1. 在 Spanner 中,使用整数主键复制源数据库中的表结构。
  2. 对于 Spanner 中包含顺序值的每个列,请创建一个序列,并将 GET_NEXT_SEQUENCE_VALUE (GoogleSQLPostgreSQL) 函数指定为该列的默认值。
  3. 将具有原始键的现有数据从源数据库迁移到 Spanner。考虑使用 Spanner 迁移工具Dataflow 模板
  4. (可选)您可以为任何依赖表建立外键约束条件
  5. 在插入新数据之前,请调整 Spanner 序列以跳过现有键值的范围。
  6. 插入新数据,让序列自动生成唯一键。

迁移工作流示例

以下代码使用 SEQUENCE 对象在 Spanner 中定义表结构和相关序列,并将该对象设置为目标表的默认主键值:

GoogleSQL

CREATE SEQUENCE singer_id_sequence OPTIONS (
     SequenceKind = 'bit_reversed_positive'
  );

CREATE TABLE Singers (
     SingerId INT64 DEFAULT
     (GET_NEXT_SEQUENCE_VALUE(SEQUENCE SingerIdSequence)),
     Name STRING(1024),
     Biography STRING(MAX),
  ) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
     AlbumId INT64,
     SingerId INT64,
     AlbumName STRING(1024),
     SongList STRING(MAX),
     CONSTRAINT FK_singer_album
     FOREIGN KEY (SingerId)
       REFERENCES Singers (SingerId)
  ) PRIMARY KEY (AlbumId);

PostgreSQL

CREATE SEQUENCE SingerIdSequence BIT_REVERSED_POSITIVE;

CREATE TABLE Singers (
  SingerId BIGINT DEFAULT nextval('SingerIdSequence') PRIMARY KEY,
  Name VARCHAR(1024) NOT NULL,
  Biography TEXT
);

CREATE TABLE Albums (
  AlbumId BIGINT PRIMARY KEY,
  SingerId BIGINT,
  AlbumName VARCHAR(1024),
  SongList TEXT,
  CONSTRAINT FK_singer_album FOREIGN KEY (SingerId) REFERENCES Singers (SingerId)
);

bit_reversed_positive 选项表示序列生成的值的类型为 INT64,大于零且不连续。

将现有行从源数据库迁移到 Spanner 时,主键保持不变。

对于未指定主键的新插入操作,Spanner 会通过调用 GET_NEXT_SEQUENCE_VALUE()GoogleSQLPostgreSQL)函数自动检索新值。

这些值均匀分布在 [1, 263] 范围内,并且可能会与现有键发生冲突。为防止出现这种情况,您可以使用 ALTER_SEQUENCEGoogleSQLPostgreSQL)配置序列,以跳过现有键涵盖的值范围。

假设 singers 表是从 PostgreSQL 迁移过来的,其中主键 singer_idSERIAL 类型。以下 PostgreSQL 显示了您的源数据库 DDL:

PostgreSQL

CREATE TABLE Singers (
SingerId SERIAL PRIMARY KEY,
Name varchar(1024),
Biography varchar
);

主键值是单调递增的。迁移完成后,您可以在 Spanner 上检索主键 singer_id 的最大值。在 Spanner 中使用以下代码:

GoogleSQL

SELECT MAX(SingerId) FROM Singers;

PostgreSQL

SELECT MAX(SingerId) FROM Singers;

假设返回值为 20,000。您可以将 Spanner 序列配置为跳过范围 [1, 21000]。额外的 1,000 个用于充当缓冲区,以便在初始迁移后向源数据库写入数据。在 Spanner 中生成的新键不会与在源 PostgreSQL 数据库中生成的主键范围冲突。在 Spanner 中使用以下代码:

GoogleSQL

ALTER SEQUENCE SingerIdSequence SET OPTIONS (
skip_range_min = 1,
skip_range_max = 21000
);

PostgreSQL

ALTER SEQUENCE SingerIdSequence SKIP RANGE 1 21000;

使用 Spanner 和源数据库

您可以使用跳过范围概念来支持 Spanner 或源数据库生成主键的场景,例如,在迁移割接期间启用任一方向的复制以进行灾难恢复。

为此,这两个数据库都会生成主键,并且数据会在它们之间同步。您可以将每个数据库配置为在不重叠的键范围内创建主键。为源数据库定义范围时,您可以配置 Spanner 序列以跳过该范围。

例如,在音乐曲目应用迁移完成后,将数据从 PostgreSQL 复制到 Spanner,以缩短切换所需的时间。

在 Spanner 上更新并测试应用后,您可以停止使用源 PostgreSQL 数据库,改用 Spanner,并将其用作更新和新主键的记录系统。Spanner 接管后,您可以将数据库之间的数据流向反转到 PostgreSQL 实例。

假设您的源 PostgreSQL 数据库使用 SERIAL 主键,即 32 位有符号整数。Spanner 主键是较大的 64 位数字。在 PostgreSQL 中,将主键列更改为 64 位列或 bigint。在源 PostgreSQL 数据库上使用以下代码:

PostgreSQL

ALTER TABLE Singers ALTER COLUMN SingerId TYPE bigint;

您可以为源 PostgreSQL 数据库中的表设置 CHECK 约束条件,以确保 SingerId 主键的值始终小于或等于 231-1。在源 PostgreSQL 数据库上使用以下代码:

PostgreSQL

ALTER TABLE Singers ADD CHECK (SingerId <= 2147483647);

在 Spanner 中,我们可以更改序列以跳过 [1, 231-1] 范围。在 Spanner 中使用以下代码:

GoogleSQL

ALTER SEQUENCE SingerIdSequence SET OPTIONS (
skip_range_min = 1,
skip_range_max = 2147483647 -- 231-1
);

PostgreSQL

ALTER SEQUENCE SingerIdSequence SKIP RANGE 1 2147483648;

源 PostgreSQL 数据库始终在 32 位整数空间中生成键,而 Spanner 键仅限于 64 位整数空间,大于所有 32 位整数值。这样可以确保两个数据库都能独立生成不会冲突的主键。

迁移 UUID 键列

无论在何处生成,UUIDv4 键都是唯一的。 在其他位置生成的 UUID 键可与在 Spanner 中生成的新 UUID 键集成。

请考虑以下概要策略,将 UUID 键迁移到 Spanner:

  1. 在 Spanner 中,使用带有默认表达式的字符串列定义 UUID 键。使用 GENERATE_UUID() 函数(GoogleSQLPostgreSQL)。
  2. 从源系统导出数据,将 UUID 键序列化为字符串。
  3. 将主键导入 Spanner。
  4. 可选:启用外键。

下面是一个迁移工作流程示例:

在 Spanner 中,将 UUID 主键列定义为 STRINGTEXT 类型,并将 GENERATE_UUID()GoogleSQLPostgreSQL)指定为其默认值。将所有数据从源数据库迁移到 Spanner。迁移后,如 插入新行,Spanner 调用 GENERATE_UUID() 为主键生成新的 UUID 值。 例如,当在表 FanClubs 中插入新行时,主键 FanClubId 会获取一个 UUIDv4 值。在 Spanner 中使用以下代码:

GoogleSQL

CREATE TABLE Fanclubs (
FanClubId STRING(36) DEFAULT (GENERATE_UUID()),
ClubName STRING(1024),
) PRIMARY KEY (FanClubId);

INSERT INTO FanClubs (ClubName) VALUES ("SwiftFanClub");

PostgreSQL

CREATE TABLE FanClubs (
  FanClubId TEXT DEFAULT spanner.generate_uuid() PRIMARY KEY,
  ClubName VARCHAR(1024)
);

INSERT INTO FanClubs (ClubName) VALUES ('SwiftFanClub');

迁移您自己的主键

您的应用可能依赖于主键顺序来确定数据的新近程度或对新创建的数据进行排序。如需在 Spanner 中使用外部生成的顺序键,您可以创建一个复合键,将均匀分布的值(例如哈希值)作为第一个组件,将顺序键作为第二个组件。这样,您就可以保留顺序键值,而不会大规模创建热点。请考虑以下迁移工作流:

假设您需要将具有 AUTO_INCREMENT 主键的 MySQL 表 students 迁移到 Spanner。在源 MySQL 数据库中使用以下代码:

MySQL

CREATE TABLE Students (
StudentId INT NOT NULL AUTO_INCREMENT,
Info VARCHAR(2048),
PRIMARY KEY (StudentId)
);

在 Spanner 中,您可以通过创建 StudentId 列的哈希来添加生成的列 StudentIdHash。例如:

  StudentIdHash = FARM_FINGERPRINT(CAST(StudentId AS STRING))

您可以在 Spanner 中使用以下代码:

GoogleSQL

CREATE TABLE student (
  StudentIdHash INT64 AS (FARM_FINGERPRINT(cast(StudentId as string))) STORED,
  StudentId INT64 NOT NULL,
  Info STRING(2048),
) PRIMARY KEY(StudentIdHash, StudentId);

PostgreSQL

CREATE TABLE Student (
  StudentIdHash bigint GENERATED ALWAYS AS
  (FARM_FINGERPRINT(cast(StudentId AS varchar))) STORED,
  StudentId bigint NOT NULL,
  Info varchar(2048),
  PRIMARY KEY (StudentIdHash, StudentId)
);

迁移顺序键列

如果您的源数据库系统为键列生成顺序值,您可以在 Spanner 架构中使用位反转的正序列 (GoogleSQLPostgreSQL) 来生成在正 64 位整数数空间中均匀分布的值。为防止 Spanner 序列生成与已迁移的值重叠的值,您可以为其定义一个跳过的范围。

例如,如果您知道源数据库仅生成 32 位整数,则可以跳过以下两个序列的 1 到 4,294,967,296 (2^32) 范围:

GoogleSQL

CREATE SEQUENCE MyFirstSequence OPTIONS (
  sequence_kind = "bit_reversed_positive",
  skip_range_min = 1,
  skip_range_max = 4294967296
);

ALTER SEQUENCE MySecondSequence SET OPTIONS (
  skip_range_min = 1,
  skip_range_max = 4294967296
);

PostgreSQL

CREATE SEQUENCE MyFirstSequence BIT_REVERSED_POSITIVE
  SKIP RANGE 1 4294967296;

ALTER SEQUENCE MySecondSequence SKIP RANGE 1 4294967296;

如果您使用 IDENTITY为键列自动生成整数值,则可以设置跳过范围:

GoogleSQL

如需设置跳过范围,请使用 GENERATED BY DEFAULT AS IDENTITY 命令:

ALTER DATABASE db SET OPTIONS (
  default_sequence_kind = 'bit_reversed_positive',
);

CREATE TABLE MyFirstTable (
  Id INT64 GENERATED BY DEFAULT AS IDENTITY (SKIP RANGE 1, 4294967296),
  Name STRING(MAX),
) PRIMARY KEY (Id);

ALTER TABLE MyFirstTable ALTER COLUMN Id ALTER IDENTITY SET SKIP RANGE 1, 4294967296;

PostgreSQL

如需设置跳过范围,请使用 GENERATED BY DEFAULT AS IDENTITY 命令:

ALTER DATABASE db
    SET spanner.default_sequence_kind = 'bit_reversed_positive';

CREATE TABLE MyFirstTable (
  Id bigint GENERATED BY DEFAULT AS IDENTITY (SKIP RANGE 1 4294967296),
  Name text,
  PRIMARY KEY (Id)
);

ALTER TABLE MyFirstTable ALTER COLUMN Id SET SKIP RANGE 1 4294967296;

迁移位反转的键列

如果您已对键值进行了位反转以避免源数据库中出现热点问题,则还可以使用经过位反转的 Spanner 正序列 (GoogleSQLPostgreSQL) 继续生成此类值。为避免生成重复值,您可以将序列配置为从自定义数字开始计数。

例如,如果您将 1 到 1000 的数字倒序来生成主键值,Spanner 序列可以从大于 10,000 的任意数字开始计数。您可以选择一个较大的数字,以便为数据迁移后在源数据库中发生的新写入保留缓冲区。在以下示例中,计数器从 11,000 开始:

GoogleSQL

CREATE SEQUENCE MyFirstSequence OPTIONS (
  sequence_kind = "bit_reversed_positive",
  start_with_counter = 11000
);

ALTER SEQUENCE MySecondSequence SET OPTIONS (
  start_with_counter = 11000
);

PostgreSQL

CREATE SEQUENCE MyFirstSequence BIT_REVERSED_POSITIVE
  START COUNTER 11000;

ALTER SEQUENCE MySecondSequence RESTART COUNTER 11000;

如果您使用 IDENTITY为键列自动生成整数值,则可以设置一个起始计数器:

GoogleSQL

如需设置开始计数器,请使用 GENERATED BY DEFAULT AS IDENTITY 命令:

ALTER DATABASE db SET OPTIONS (
  default_sequence_kind = 'bit_reversed_positive',
);

CREATE TABLE MyFirstTable (
  Id INT64 GENERATED BY DEFAULT AS IDENTITY (START COUNTER WITH 11000),
  Name STRING(MAX),
) PRIMARY KEY (Id);

ALTER TABLE MyFirstTable ALTER COLUMN Id ALTER IDENTITY RESTART COUNTER WITH 11000;

PostgreSQL

如需设置开始计数器,请使用 GENERATED BY DEFAULT AS IDENTITY 命令:

ALTER DATABASE db
    SET spanner.default_sequence_kind = 'bit_reversed_positive';

CREATE TABLE MyFirstTable (
  Id bigint GENERATED BY DEFAULT AS IDENTITY (START COUNTER WITH 11000),
  Name text,
  PRIMARY KEY (Id)
);

ALTER TABLE MyFirstTable ALTER COLUMN Id RESTART COUNTER WITH 11000;

后续步骤