使用并发写入更新大型 Spanner table 中的列

Updating column in large Spanner table with concurrent writes

我想更新大型 table(数千万行)中的列,而不会使我们的应用程序停机;即,将同时写入 table。我想在代码中执行此操作 (Java),因为更新非常重要,我想知道最好的方法是什么。

一种有效的方法是打开一个查询所有行的读取事务,然后遍历所有这些行 (resultSet.next()),同时创建一系列 read/write 事务,比如说, 10,000 INSERT_OR_UPDATE 个突变缓冲在每个事务中,包含所需的更新。

问题是这无法处理并发写入,因为可能会发生以下步骤:

  1. 上面提到的读取事务读取X行
  2. 一些单独的事务更新行 X
  3. 上面提到的read/write交易使用了第1步的数据并覆盖了第2步的更新

为了解决这个问题,我可以在 read/write 事务期间读回值并验证它没有改变,类似于 this example here,但是这看起来很慢(每次约 50 毫秒调用,这意味着需要几周的时间来更新整个 table)。

那么我怎样才能更有效地做到这一点呢?谢谢!

最好的方法是不使用 read-only 事务,而是为每批 10,000 条记录启动一个 read/write 事务,读取您要在此更新的值read/write 事务,然后在同一个 read/write 事务中更新这 10,000 条记录。重复此操作,直到所有记录都已更新。

所以像这样:

  1. 开始read/write交易
  2. 读取一批 10,000 条记录。通过对主键或其他一些唯一(组合)列进行排序,确保记录的顺序一致。使用 LIMITOFFSET 来限制结果,所以你会得到这样的查询 SELECT * FROM SOME_TABLE WHERE KEY>=@start AND KEY<@end LIMIT 10000
  3. 更新记录并提交事务。
  4. 重复直到所有记录都被更新。

您没有说明要如何更新列 - 无论是设置常量值,还是根据行中的其他列计算值。

您也没有说 table 的其他更新是什么,但我认为它们将涉及对此列的修改,或对影响此列的其他列的修改..

无论如何,partitioned DML is a solution to this... 以更新语句的形式表达你的修改:

UPDATE table SET col1=123 WHERE col2=TRUE

然后 运行 作为分区 DML(使用 API 或 gcloud with the --enable-partitioned-dml flag)Spanner 会将操作拆分为多个单独的事务,其中每个事务在内部都是一致的。每个 DML 事务将仅锁定 table 中的 sub-set 行,同时 运行 处理事务。

分区 DML 的一个问题是表达式将在每一行上 运行 至少一次 - 由于重试 - 因此语句必须是幂等的 -即多次执行结果相同