数据操纵语言最佳做法

本页面介绍使用数据操纵语言 (DML) 和分区 DML 的最佳做法。

使用 WHERE 子句缩小锁定范围

在读写事务中执行 DML 语句。Spanner 读取数据时,会在您读取的部分行范围上获取共享读取锁。具体而言,它仅会对您访问的列获取这些锁定。锁可以包含不符合 WHERE 子句过滤条件的数据。

当 Spanner 使用 DML 语句修改数据时,它会对您要修改的特定数据获取独占锁定。此外,它还会采用与读取数据时相同的方式获取共享锁定。如果您的请求包含较大的行范围或整个表,则共享锁定可能会阻止其他事务并行进行。

如需尽可能高效地修改数据,请使用 WHERE 子句,使 Spanner 仅读取必要的行。您可以通过按主键进行过滤或按二级索引的键进行过滤来实现此目标。WHERE 子句限制了共享锁定的范围,使 Spanner 能够更高效地处理更新。

例如,假设 Singers 表中的某位音乐人更改了其名字,则您需要在您的数据库中更新该名字。您可以执行以下 DML 语句,但会强制 Spanner 扫描整个表并获取覆盖整个表的共享锁。因此,Spanner 必须读取更多的数据,并且并发事务不能并行修改数据:

-- ANTI-PATTERN: SENDING AN UPDATE WITHOUT THE PRIMARY KEY COLUMN
-- IN THE WHERE CLAUSE

UPDATE Singers SET FirstName = "Marcel"
WHERE FirstName = "Marc" AND LastName = "Richards";

为使该更新更加高效,请在 WHERE 子句中添加 SingerId 列。SingerId 列是 Singers 表的唯一主键列:

-- ANTI-PATTERN: SENDING AN UPDATE THAT MUST SCAN THE ENTIRE TABLE

UPDATE Singers SET FirstName = "Marcel"
WHERE FirstName = "Marc" AND LastName = "Richards"

如果 FirstNameLastName 没有索引,您需要扫描整个表来查找目标歌手。如果您不想添加二级索引以提高更新效率,请在 WHERE 子句中包含 SingerId 列。

SingerId 列是 Singers 表的唯一主键列。如需找到它,请在更新事务之前在单独的只读事务中运行 SELECT


  SELECT SingerId
  FROM Singers
  WHERE FirstName = "Marc" AND LastName = "Richards"

  -- Recommended: Including a seekable filter in the where clause

  UPDATE Singers SET FirstName = "Marcel"
  WHERE SingerId = 1;

避免在同一事务中使用 DML 语句和变更

Spanner 可在服务器端缓冲使用 DML 语句执行的插入、更新和删除操作,并且结果对同一事务中的后续 SQL 和 DML 语句可见。此行为与 Mutation API 不同,在 Mutation API 中,Spanner 会在客户端缓冲变更,并将变更作为提交操作的一部分发送到服务器端。因此,提交请求中的变更对同一事务中的 SQL 或 DML 语句不可见。

避免在同一事务中同时使用 DML 语句和变更。如果您要在同一事务中同时使用这两者,则需要考虑客户端库代码中的执行顺序。如果事务在同一请求中同时包含 DML 语句和变更,则 Spanner 会在变更之前执行 DML 语句。

对于仅支持变更的操作,您可能希望在同一事务中组合 DML 语句和变更,例如 insert_or_update

如果同时使用这两者,缓冲区只会在事务结束时写入。

使用 PENDING_COMMIT_TIMESTAMP 函数写入提交时间戳

GoogleSQL

使用 PENDING_COMMIT_TIMESTAMP 函数在 DML 语句中写入提交时间戳。Spanner 会在事务提交时选择提交时间戳。

PostgreSQL

使用 SPANNER.PENDING_COMMIT_TIMESTAMP() 函数在 DML 语句中写入提交时间戳。Spanner 会在事务提交时选择提交时间戳。

分区 DML 以及日期和时间戳函数

分区 DML 使用可能在不同时间运行和提交的一个或多个事务。如果使用日期时间戳函数,则修改的行可能包含不同的值。

使用批处理 DML 缩短延迟时间

为缩短延迟时间,请使用批量 DML 在一次客户端-服务器往返中将多个 DML 语句发送到 Spanner。

批处理 DML 可以对一个批次中的语句组应用优化,以实现更快、更高效的数据更新。

  • 使用单个请求执行写入

    Spanner 会自动优化具有不同参数值的相似 INSERT、UPDATE 或 DELETE 批量语句的连续组,前提是它们没有违反数据依赖关系。

    例如,设想一个场景,您希望将大量新行插入名为 Albums 的表中。如需让 Spanner 将所有必需的 INSERT 语句优化为单个高效的服务器端操作,请首先编写一个使用 SQL 查询参数的适当 DML 语句:

    INSERT INTO Albums (SingerId, AlbumId, AlbumTitle) VALUES (@Singer, @Album, @Title);
    

    然后,向 Spanner 发送一个反复且连续调用此语句的 DML 批次,其重复频率仅在绑定到语句的三个查询参数的值上有所不同。在执行之前,Spanner 会将这些结构上相同的 DML 语句优化为单个服务器端操作。

  • 并行执行写入

    Spanner 会通过并行执行来自动优化连续的 DML 语句组,这样做不违反数据依赖关系。这项优化可为更广泛的批处理 DML 语句集带来性能优势,因为它既可应用于 DML 语句类型(INSERT、UPDATE 和 DELETE)混合,也可同时应用于参数化或非参数化 DML 语句。

    例如,我们的示例架构包含表 SingersAlbumsAccountsAlbums 交错在 Singers 中,用于存储 Singers 的影集相关信息。下面这组连续语句会将新行写入多个表,并且没有复杂的数据依赖关系。

    INSERT INTO Singers (SingerId, Name) VALUES(1, "John Doe");
    INSERT INTO Singers (SingerId, Name) VALUES(2, "Marcel Richards");
    INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (1, 10001, "Album 1");
    INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (1, 10002, "Album 2");
    INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (2, 10001, "Album 1");
    UPDATE Accounts SET Balance = 100 WHERE AccountId = @AccountId;
    

    Spanner 通过并行执行这组 DML 语句来优化这些语句。系统会按批处理中的语句顺序应用写入,并且如果语句在执行期间失败,系统会保留批处理 DML 语义。