TransactionCanceledException 中神秘的事务冲突

问题描述 投票:0回答:2

在 aws-sdk (js) 中使用 transactWriteItems,我们得到一个 TransactionCanceledException。该异常中的原因被指定为TransactionConflict。有时事务中的所有操作都会失败,有时只有少数或只有一个。我们确实并行运行多个可以对相同项目进行操作的事务。文档没有提到这个特定的错误。可能原因摘录:

  1. 不满足条件表达式之一中的条件。
  2. TransactWriteItems 请求中的表位于不同的帐户中或 地区。
  3. TransactWriteItems 操作中的多个操作都针对 同一件商品。
  4. 没有足够的预置容量来进行交易 已完成。
  5. 项目大小变得太大(大于 400 KB),或者本地 二级索引(LSI)变得太大,或者类似的验证错误 由于交易所做的更改而发生。
  6. 存在用户错误,例如数据格式无效。

这些都不适用,当重试交易时,它似乎最终起作用了。有人知道这个例外吗?我找不到任何记录。

amazon-web-services transactions amazon-dynamodb aws-sdk
2个回答
29
投票

您遇到的不是错误 - 它实际上是功能的一部分,并且在发布公告中提到过

交易期间物品不会被锁定。 DynamoDB 事务提供可序列化的隔离。如果在事务处理过程中在事务外修改了某个项目,则该事务将被取消,并引发异常,并包含有关哪个或哪些项目导致异常的详细信息。

顺便说一句,DynamoDB 使用一种称为乐观并发控制(也(令人困惑地)称为乐观锁定)的东西来代替锁定。如果您有兴趣了解更多信息,维基百科上关于“乐观并发控制”的文章非常好。 回到手头的事情,AWS

交易文档

说:

多个事务同时更新相同的项目可能会导致冲突,从而取消事务。我们建议遵循 DynamoDB 数据建模最佳实践,以尽量减少此类冲突。

特别是对于 TransactWriteItems,他们说:

以下情况写交易不会成功:

当正在进行的 TransactWriteItems 操作与 TransactWriteItems 操作中的一项或多项的并发 TransactWriteItems 请求发生冲突时。在这种情况下,并发请求失败并出现 TransactionCancelledException

TransactGetItems 类似:

以下情况读取事务不会成功:

当正在进行的 TransactGetItems 操作与并发 PutItem、UpdateItem、DeleteItem 或 TransactWriteItems 请求冲突时。在这种情况下,TransactGetItems 操作失败并出现 TransactionCancelledException


0
投票
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}.")

	
© www.soinside.com 2019 - 2024. All rights reserved.