比较 DML 和变更

数据操纵语言 (DML) 和变更是 Cloud Spanner 中可供您用于修改数据的两个 API。它们各自提供类似的数据操纵特性。本页面对两种方法进行了比较。

什么是数据操纵语言 (DML)?

借助 Cloud Spanner 中的数据操纵语言 (DML),您可以使用 INSERT、UPDATE 和 DELETE 语句来操纵数据库表中的数据。您可以使用客户端库Cloud Consolegcloud spanner 运行 DML 语句。

Cloud Spanner 提供以下两个 DML 执行的实现,每个实现具有不同的属性。

  • 标准 DML - 适用于标准联机事务处理 (OLTP) 工作负载。

    如需了解详情(包括代码示例),请参阅使用 DML

  • 分区 DML - 专为批量更新和删除而设计,如以下示例所示。

    • 定期清理和垃圾回收。例如,删除旧行或将列设置为 NULL。

    • 使用默认值回填新列。例如,使用 UPDATE 语句将新列当前的 NULL 值设置为 False。

    如需了解详情(包括代码示例),请参阅使用分区 DML

什么是变更?

变更表示插入、更新和删除等一系列操作,Cloud Spanner 将这些操作以原子方式应用于数据库中不同的行和表。您可以在变更中添加应用于不同行或不同表的操作。在定义一个或多个变更(其中包含一个或多个写入)之后,必须应用该更改来提交写入。每项更改均按照它们添加到更改的顺序进行应用。

如需了解详情(包括代码示例),请参阅使用变更插入、更新和删除数据

DML 与变更之间的特性比较

下表汇总了常见数据库操作和特性的 DML 和变更支持。

操作 DML 变更
插入数据 支持 支持
删除数据 支持 支持
更新数据 支持 支持
读己所写 (RYW) 支持 不支持
插入/更新(插入或更新) 不支持 支持
SQL 语法 支持 不支持
限制条件检查 在每个语句之后 提交时

DML 和变更对下列功能提供支持。

  • 读己所写语义 - “读己所写”是指在活跃事务中读取未提交的结果。使用 DML 语句所做的更改对同一事务中的后续语句可见。这与使用变更不同,使用变更所做的更改在事务提交之前在任何读取(包括在同一事务中完成的读取)中都不可见。这是因为事务中的变更会在客户端(本地)缓冲,并在执行提交操作的过程中发送到服务器。因此,提交请求中的变更对同一事务中的 SQL 或 DML 语句不可见。

  • 插入/更新 - DML 不支持原生插入/更新,插入/更新是一种将行插入到数据库表(如果表中还没有行)或更新行(如果表中存在行)的操作。不过,在变更领域存在一项“插入或更新”操作。使用 DML 的应用可以通过以下方法解决此限制:首先读取行,然后使用适当的 DML 语句。

  • 限制条件检查 - Cloud Spanner 会在每个 DML 语句之后检查限制条件。这与使用变更不同,使用变更时,Cloud Spanner 在提交之前缓冲客户端中的变更,并在提交时检查限制条件。在每个 DML 语句之后评估限制条件可使 Cloud Spanner 保证由同一事务中的后续查询返回的数据与架构一致。

  • SQL 语法 - DML 提供了一种用于操作数据的传统方法。您可以重复使用 SQL 技能,以便使用 DML API 更改数据。

最佳做法 - 避免在同一事务中混用 DML 和变更

如果事务在提交请求中同时包含 DML 语句和变更,则 Cloud Spanner 会在变更之前执行 DML 语句。为了避免在客户端库代码中考虑执行顺序,您应该在单个事务中使用 DML 语句或变更,但不要同时使用两者。

以下 Java 示例说明了可能会出现的意外行为。代码使用 Mutation API 将两行插入 Albums。然后,代码段调用 executeUpdate() 来更新新插入的行,并调用 executeQuery() 来读取更新后的 Albums。

static void updateMarketingBudget(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(
          new TransactionCallable<Void>() {
            @Override
            public Void run(TransactionContext transaction) throws Exception {
               transaction.buffer(
                    Mutation.newInsertBuilder("Albums")
                        .set("SingerId")
                        .to(1)
                        .set("AlbumId")
                        .to(1)
                        .set("AlbumTitle")
                        .to("Total Junk")
                        .set("MarketingBudget")
                        .to(800)
                        .build());
               transaction.buffer(
                    Mutation.newInsertBuilder("Albums")
                        .set("SingerId")
                        .to(1)
                        .set("AlbumId")
                        .to(2)
                        .set("AlbumTitle")
                        .to("Go Go Go")
                        .set("MarketingBudget")
                        .to(200)
                        .build());

                // This UPDATE will not include the Albums inserted above.
                String sql =
                  "UPDATE Albums SET MarketingBudget = MarketingBudget * 2"
                      + " WHERE SingerId = 1";
                long rowCount = transaction.executeUpdate(Statement.of(sql));
                System.out.printf("%d records updated.\n", rowCount);

                // Read a newly updated record.
                sql =
                  "SELECT SingerId, AlbumId, AlbumTitle FROM Albums"
                      + " WHERE SingerId = 1 AND MarketingBudget < 1000";
                ResultSet resultSet =
                                 transaction.executeQuery(Statement.of(sql));
                while (resultSet.next()) {
                   System.out.printf(
                        "%s %s\n",
                        resultSet.getString("FirstName"),
                        resultSet.getString("LastName"));
                }
                return null;
              }
            });
}

如果执行此代码,则会看到 0 条记录已更新。为什么?之所以发生这种情况,是因为我们使用变更所做的更改在事务提交之前对后续语句不可见。理想情况下,应仅在事务的最后缓冲写入。

后续步骤