在 Google Spanner 中插入(而不是更新)时的自动 created_on(不是 updated_on)时间戳
Automatic created_on (not updated_on) timestamp on insert (not update) in Google Spanner
据我所知,没有安全的方法(在并发环境中)执行此操作,但我想确保我没有遗漏任何内容。
通常,在我们的数据库中,我们喜欢跟踪一行最初创建的时间和最后更新的时间。分别地。这不是 "created_at" 列,实际上应该称为 "updated_on"。
在 Spanner 中,使用提交时间戳(或者甚至只是始终输入当前时间),updated_on 很容易。但是,我使用的常用工具 created_on:
- 默认值,永不更新
- 重复键 ...
- 触发器
好像没有。我想,也许你可以设置一个云函数,这看起来有点矫枉过正(具有讽刺意味的是,云函数会矫枉过正......)。
我能想到的最接近但并不奇怪的事情是尝试插入突变,捕获异常,检查 ErrorCode.ALREADY_EXISTS,然后更新。并且只在插入块中设置 created_on 。丑陋的......而且在面对并发删除时也不是很安全(你插入,你发现错误,中间有人删除,尝试更新,繁荣)
还有其他建议吗?最好通过 SDK?
我可以想到两种可能的解决方案:
您可以添加两列,一列用于 created_at,一列用于 updated_on。插入行时,将 created_at 和 updated_on 设置为 spanner.commit_timestamp() 占位符。更新行时,只需将 updated_on 更改为 spanner.commit_timestamp()。
创建一个事务来封装突变。在单笔交易中,您可以:
- 读取 table 以检查该行是否存在
- 如果该行已存在,则更新该行
- 如果该行不存在,则插入该行
如果您在单个事务中执行这些操作,您将避免您提到的竞争条件,因为事务是隔离的。
可以在此处找到有关提交时间戳的更多信息:https://cloud.google.com/spanner/docs/commit-timestamp
请注意,正如我在评论中所阐明的那样,我正在寻找干净的东西,而不是交易,因为交易的工作方式有点难看(接口至少应该有 lambdas 作为一个选项,而不是匿名 类 :/.) 或者更好,只是 beginTransaction()、endTransaction(),例如:
this.dbClient.readWriteTransaction()
.run(
new TransactionCallable<Void>() {
@Nullable
@Override
public Void run(TransactionContext transactionContext) throws Exception {
Struct row = transactionContext.readRow(
MY_TABLE,
Key.of(
keyCol1,
keyCol2
),
Collections.singletonList(keyCol1)
);
//this creates a write builder with a bunch of columns
//set to mutated, except for CREATED_ON
WriteBuilder writeBuilder = updateBuilder(
Mutation.newInsertBuilder(MY_TABLE),
myDataModel
);
if(row == null) {
writeBuilder.set(CREATED_ON).to(Timestamp.now()).build();
}
Mutation recMut =
updateBuilder(Mutation.newUpdateBuilder(MY_TABLE), myDataModel).build();
transactionContext.buffer(recMut);
return null;
}
}
);
@RedPandaCurious 是正确的,Scott 的回答只成功了一半:(1) 注定会失败,原因在问题中列出 - 或者采取另一种方式,只是重新说明我想要完成的事情,而没有说明(2) 如何只是重述我的后续评论,而没有提供任何更多细节或文档。
@RedPandaCurious,如果你想注意到事务比捕获异常更快,有一些相关的文档(我特别好奇它们是否更快,总体而言,对于各种工作负载,面对许多并发操作,对于处理异常的一个客户端来说不一定更快),这作为一个答案是有意义的。不过,最终,交易是最正确、最理智的推理方式。这就是为什么我最终采用这种方法的原因 - 因为任何一种方法都很丑陋。
好吧,事实证明,如果你删除 @Nullable 注释,你可以使用 lambdas,并且通过一些额外的重构,将其减少到:
/**
* Lambda interface for factoring out transactional execution logic
*/
public interface SpannerOperation {
Boolean doOperation(TransactionContext ctxt, Struct row);
}
private Boolean executeIfExists(
... key fields ...
SpannerOperation spannerOperationIfExists,
SpannerOperation spannerOperationifNotExists,
Iterable<String> columns
) {
return this.dbClient.readWriteTransaction().run(
transactionContext -> {
Struct row = transactionContext.readRow(
MY_TABLE,
Key.of(...), //you could even pass the key in as a key
columns
);
if(row != null) {
spannerOperation.doOperation(transactionContext, row);
return true;
} else {
spannerOperationifNotExists.doOperation(transactionContext, null);
return false;
}
}
);
}
public boolean doSomething(... keyStuff.. )
return this.executeIfExists(
.. key fields ...
(ctxt, row) -> {
Mutation mut = Mutation
.newUpdateBuilder(MY_TABLE)
.....//as you like it...
.build()
ctxt.buffer(mut);
return true;
},
(ctxt, row) -> false, //set created_on or whatever
Collections.singleton(..some column you want to read in..)
);
请注意,这也适用于附加到列表等内容,并且所有这些都被分解为您需要的内容。 Google 确实需要一个 ifExists() 方法 - 我最终在很多地方使用了它..;)
据我所知,没有安全的方法(在并发环境中)执行此操作,但我想确保我没有遗漏任何内容。
通常,在我们的数据库中,我们喜欢跟踪一行最初创建的时间和最后更新的时间。分别地。这不是 "created_at" 列,实际上应该称为 "updated_on"。
在 Spanner 中,使用提交时间戳(或者甚至只是始终输入当前时间),updated_on 很容易。但是,我使用的常用工具 created_on:
- 默认值,永不更新
- 重复键 ...
- 触发器
好像没有。我想,也许你可以设置一个云函数,这看起来有点矫枉过正(具有讽刺意味的是,云函数会矫枉过正......)。
我能想到的最接近但并不奇怪的事情是尝试插入突变,捕获异常,检查 ErrorCode.ALREADY_EXISTS,然后更新。并且只在插入块中设置 created_on 。丑陋的......而且在面对并发删除时也不是很安全(你插入,你发现错误,中间有人删除,尝试更新,繁荣)
还有其他建议吗?最好通过 SDK?
我可以想到两种可能的解决方案:
您可以添加两列,一列用于 created_at,一列用于 updated_on。插入行时,将 created_at 和 updated_on 设置为 spanner.commit_timestamp() 占位符。更新行时,只需将 updated_on 更改为 spanner.commit_timestamp()。
创建一个事务来封装突变。在单笔交易中,您可以:
- 读取 table 以检查该行是否存在
- 如果该行已存在,则更新该行
- 如果该行不存在,则插入该行
如果您在单个事务中执行这些操作,您将避免您提到的竞争条件,因为事务是隔离的。
可以在此处找到有关提交时间戳的更多信息:https://cloud.google.com/spanner/docs/commit-timestamp
请注意,正如我在评论中所阐明的那样,我正在寻找干净的东西,而不是交易,因为交易的工作方式有点难看(接口至少应该有 lambdas 作为一个选项,而不是匿名 类 :/.) 或者更好,只是 beginTransaction()、endTransaction(),例如:
this.dbClient.readWriteTransaction()
.run(
new TransactionCallable<Void>() {
@Nullable
@Override
public Void run(TransactionContext transactionContext) throws Exception {
Struct row = transactionContext.readRow(
MY_TABLE,
Key.of(
keyCol1,
keyCol2
),
Collections.singletonList(keyCol1)
);
//this creates a write builder with a bunch of columns
//set to mutated, except for CREATED_ON
WriteBuilder writeBuilder = updateBuilder(
Mutation.newInsertBuilder(MY_TABLE),
myDataModel
);
if(row == null) {
writeBuilder.set(CREATED_ON).to(Timestamp.now()).build();
}
Mutation recMut =
updateBuilder(Mutation.newUpdateBuilder(MY_TABLE), myDataModel).build();
transactionContext.buffer(recMut);
return null;
}
}
);
@RedPandaCurious 是正确的,Scott 的回答只成功了一半:(1) 注定会失败,原因在问题中列出 - 或者采取另一种方式,只是重新说明我想要完成的事情,而没有说明(2) 如何只是重述我的后续评论,而没有提供任何更多细节或文档。
@RedPandaCurious,如果你想注意到事务比捕获异常更快,有一些相关的文档(我特别好奇它们是否更快,总体而言,对于各种工作负载,面对许多并发操作,对于处理异常的一个客户端来说不一定更快),这作为一个答案是有意义的。不过,最终,交易是最正确、最理智的推理方式。这就是为什么我最终采用这种方法的原因 - 因为任何一种方法都很丑陋。
好吧,事实证明,如果你删除 @Nullable 注释,你可以使用 lambdas,并且通过一些额外的重构,将其减少到:
/**
* Lambda interface for factoring out transactional execution logic
*/
public interface SpannerOperation {
Boolean doOperation(TransactionContext ctxt, Struct row);
}
private Boolean executeIfExists(
... key fields ...
SpannerOperation spannerOperationIfExists,
SpannerOperation spannerOperationifNotExists,
Iterable<String> columns
) {
return this.dbClient.readWriteTransaction().run(
transactionContext -> {
Struct row = transactionContext.readRow(
MY_TABLE,
Key.of(...), //you could even pass the key in as a key
columns
);
if(row != null) {
spannerOperation.doOperation(transactionContext, row);
return true;
} else {
spannerOperationifNotExists.doOperation(transactionContext, null);
return false;
}
}
);
}
public boolean doSomething(... keyStuff.. )
return this.executeIfExists(
.. key fields ...
(ctxt, row) -> {
Mutation mut = Mutation
.newUpdateBuilder(MY_TABLE)
.....//as you like it...
.build()
ctxt.buffer(mut);
return true;
},
(ctxt, row) -> false, //set created_on or whatever
Collections.singleton(..some column you want to read in..)
);
请注意,这也适用于附加到列表等内容,并且所有这些都被分解为您需要的内容。 Google 确实需要一个 ifExists() 方法 - 我最终在很多地方使用了它..;)