比较 DML 和变更

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

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

借助 Spanner 中的数据操纵语言 (DML),您可以使用 INSERTUPDATEDELETE 语句来操纵数据库表中的数据。您可以使用 客户端库Google Cloud 控制台gcloud spanner

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

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

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

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

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

    • 使用默认值回填新列。示例使用 UPDATE 语句将新列的值设置为 False。

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

    您可以使用批量写入执行大量无需读取的写入操作 不需要原子化事务的操作如需更多信息 请参阅使用批量写入修改数据

什么是变更?

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

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

DML 与变更之间的特性比较

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

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

DML 和变更在对以下功能的支持方面有所不同:

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

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

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

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

如果事务在提交中同时包含 DML 语句和变更 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 条记录已更新。为什么?之所以发生这种情况,是因为我们使用变更所做的更改在事务提交之前对后续语句不可见。理想情况下,应仅在事务的最后缓冲写入。

后续步骤