在 aws-sdk (js) 中使用 transactWriteItems,我们得到一个 TransactionCanceledException。该异常中的原因被指定为TransactionConflict。有时事务中的所有操作都会失败,有时只有少数或只有一个。我们确实并行运行多个可以对相同项目进行操作的事务。文档没有提到这个特定的错误。可能原因摘录:
- 不满足条件表达式之一中的条件。
- TransactWriteItems 请求中的表位于不同的帐户中或 地区。
- TransactWriteItems 操作中的多个操作都针对 同一件商品。
- 没有足够的预置容量来进行交易 已完成。
- 项目大小变得太大(大于 400 KB),或者本地 二级索引(LSI)变得太大,或者类似的验证错误 由于交易所做的更改而发生。
- 存在用户错误,例如数据格式无效。
这些都不适用,当重试交易时,它似乎最终起作用了。有人知道这个例外吗?我找不到任何记录。
您遇到的不是错误 - 它实际上是功能的一部分,并且在发布公告中提到过。
交易期间物品不会被锁定。 DynamoDB 事务提供可序列化的隔离。如果在事务处理过程中在事务外修改了某个项目,则该事务将被取消,并引发异常,并包含有关哪个或哪些项目导致异常的详细信息。
顺便说一句,DynamoDB 使用一种称为乐观并发控制(也(令人困惑地)称为乐观锁定)的东西来代替锁定。如果您有兴趣了解更多信息,维基百科上关于“乐观并发控制”的文章非常好。 回到手头的事情,AWS
交易文档多个事务同时更新相同的项目可能会导致冲突,从而取消事务。我们建议遵循 DynamoDB 数据建模最佳实践,以尽量减少此类冲突。特别是对于 TransactWriteItems,他们说:
以下情况写交易不会成功:
当正在进行的 TransactWriteItems 操作与 TransactWriteItems 操作中的一项或多项的并发 TransactWriteItems 请求发生冲突时。在这种情况下,并发请求失败并出现 TransactionCancelledException
TransactGetItems 类似:
以下情况读取事务不会成功:
当正在进行的 TransactGetItems 操作与并发 PutItem、UpdateItem、DeleteItem 或 TransactWriteItems 请求冲突时。在这种情况下,TransactGetItems 操作失败并出现 TransactionCancelledException
TransactionConflict
,遵循此
亚马逊博客中的方法 4。使用以下函数捕获异常并重试处理失败
def perform_transact_write(table_name, cluster_id, crt):
attempt = 0
delay = BASE_DELAY
dynamodb = boto3.client("dynamodb")
while attempt < MAX_RETRIES:
try:
# Define the transaction items
TransactItems = [
{
"Update": {
"TableName": table_name,
"Key": {"cisClusterId": {"S": cluster_id}},
"UpdateExpression": "ADD #o :inc",
"ExpressionAttributeNames": {"#o": "numWorkloadCluster"},
"ExpressionAttributeValues": {":inc": {"N": "1"}},
}
}
]
print(f"Attempt {attempt + 1}: Running TransactItems: {TransactItems}")
# Perform the transactional write
response = dynamodb.transact_write_items(
TransactItems=TransactItems, ClientRequestToken=crt
)
print(f"Transaction successful. CRT: {crt}")
return response # Success, exit the function
except ClientError as e:
error_code = e.response["Error"]["Code"]
if error_code == "TransactionCanceledException":
cancellation_reasons = e.response["CancellationReasons"]
# Check if any reason is TransactionConflict
if any(
reason.get("Code") == "TransactionConflict"
for reason in cancellation_reasons
):
print(
f"Caught TransactionConflict for CRT {crt}. Retrying after {delay:.2f} seconds..."
)
time.sleep(delay + random.uniform(0, 0.1))
# Exponential backoff
delay = min(delay * BACKOFF_MULTIPLIER, MAX_DELAY)
attempt += 1
else:
print(
f"Transaction canceled for CRT {crt} with reasons: {cancellation_reasons}"
)
raise
else:
# For other exceptions, re-raise
print(f"Unexpected error for CRT {crt}: {e}")
raise
# If all retries failed, raise an exception
raise Exception(f"Transaction failed after {MAX_RETRIES} retries for CRT {crt}.")