在 Google Cloud Spanner 中插入或读取(如果记录已存在)的最高效方式

Most performant way to Insert or Read(if record already exists) in Google Cloud Spanner

假设我有一个 cars table,其中 vin 是主键。 我想插入一条记录(在交易中)或读取记录(如果已经存在具有相同 PK 的记录)。

如果已经存在具有相同 PK 的记录,插入或读取记录的最佳方式是什么?

这是我目前的做法:

案例A:记录不存在

  1. 插入记录
  2. Return 记录

情况B:记录已经存在

  1. 插入记录
  2. 检查错误是否是由于记录已经存在
  3. 阅读记录
  4. Return 记录
const car = { vin: '123', make: 'honda', model: 'accord' };

spannerDatabase.runTransactionAsync(async (databaseTransaction) => {
    try {
        // Try to insert car
        await databaseTransaction.insert('cars', car);
        await databaseTransaction.commit();
        return car;
    } catch (error) {
        await databaseTransaction.end();
        // Spanner "row already exists" error. Insert failed because there is already a record with the same vin(PK)
        if (error.code === 6) {
            // Since the record already exists, I want to read it and return it. Whats the most performant way to do this?
            const existingRecord = await carsTable.read({
                columns: ['vin', 'make', 'model'],
                keys: [car.vin],
                json: true,
            });
            return existingRecord;
        }
    }
})

正如@skuruppu 在上面的评论中提到的,您当前的示例大部分都符合您所描述的内容。然而,它确实隐含地假设了几件事,因为您没有在同一事务中执行读取和插入。这意味着这两个操作不是原子的,其他事务可能会更新或删除您的两个操作之间的记录。

此外,您的方法假设场景 A(记录不存在)是最有可能的。如果不是这种情况,并且记录 确实存在 的可能性很小,那么您应该在写入之前在事务中执行读取。

如果有其他进程可能会删除记录,您也应该这样做。否则,另一个进程可能会在您尝试插入记录 之后删除记录,但在您尝试读取记录 之前(在事务之外)。

以上只是一个真正的问题如果还有其他进程可能会删除或更改记录。如果不是这样,以后也不会,这只是理论上的问题。

总结一下:

  1. 你的例子很好如果场景A是最有可能的并且没有其他进程会删除[=10中的任何记录=] table.
  2. 如果 1 中的任何条件不成立,您应该使用相同的 read/write 事务在写入之前执行读取。

您在示例中使用的 read 操作是从 table.

中读取单行的最有效方式