【问题标题】:IDbTransaction.Commit returns before transaction is completeIDbTransaction.Commit 在事务完成之前返回
【发布时间】:2021-02-15 21:47:19
【问题描述】:

我有一个服务器(很少)动态创建数据库行以适应用户配置的数据模型。在启动期间,服务器可能必须创建大约一千行以及多个插入到现有表中。

完成所有这些后,它会提交事务并向可能正在监听的任何人发送有关新数据模型的通知。问题是,transaction.Commit() 似乎在数据库实际完成更改之前返回,因此如果客户端在发送通知后向服务器发出请求,客户端可能会得到一个空结果。我的假设是等待 transaction.Commit() 将确保事务全部完成并提交。

客户端得到空结果的原因是,当不做 DDL 操作时,数据库正在使用快照隔离,所以很明显快照是在 DDL 操作完成之前拍摄的(但在 transaction.commit 返回之后)

操作顺序为:

  1. 开始交易
  2. 在数据库中执行数千次操作 (DDL)
  3. 提交事务
  4. 发送有关更改的通知
  5. 客户端请求数据(快照隔离)
  6. 没有数据(交易前的状态)返回给客户端。

为什么 transaction.Commit() 在事务完成提交之前完成?如何让服务器等待事务完全完成后再继续发送通知?

编辑:清晰。

【问题讨论】:

  • 也许你有一个外部交易同时进行?然后,在外部事务提交之前,您的 Commit 实际上不会应用更改。
  • 这当然是一种可能性(这是一个非常成熟的框架,所以不是很容易导航),但我相当肯定它不是这种情况。不过,我会进行调查 - 正如我在下面所写的那样,在我提交后我关闭了连接,因此父事务几乎不会阻塞。如果我们假设情况并非如此,那么这似乎只在涉及 DDL 时才会发生。提交完成后,正常的数据库更新似乎已完全结束。
  • 当您说 DDL 时,您是指 CREATE 语句吗? DDL 将使用模式修改锁并阻止其他会话,而不管事务隔离级别如何,所以也许您的意思是 DML(插入)。我建议您运行跟踪以验证事务的顺序。这些症状表明客户端请求 (#5) 在步骤 #1 之前开始的快照事务中完成。
  • 我的意思是 DDL - 不在我读过的桌子上。我在一些表中添加列,在其他表中添加行,以便在单个事务中记账。 @Evk 我确实有一个外部 TransactionScope,但是抑制它并不能解决问题。
  • @Evk - 确实是外部 TransactionScope 造成了麻烦!让它成为一个正确的答案,你就会得到一个正确的仪式。

标签: c# sql-server ado.net ddl


【解决方案1】:

正如我们在 cmets 中发现的,一个可能的原因是待处理的外部事务。如documentation中所述:

在嵌套事务中使用时,内部事务的提交 不要释放资源或使其修改永久化。数据 修改是永久性的,资源只有在 外部事务已提交。每个 COMMIT TRANSACTION 发出的时间 @@TRANCOUNT 大于 1,只需将 @@TRANCOUNT 减 1。 当@@TRANCOUNT 最终减为 0 时,整个外层 事务已提交。因为 transaction_name 被忽略 数据库引擎,发出一个引用名称的 COMMIT TRANSACTION 有未完成的内部事务时的外部事务 仅将 @@TRANCOUNT 减 1。

因此,尽管Commit() 已成功执行,但如果存在外部事务 - 在外部事务也提交之前,不会将任何更改持久保存到数据库。

【讨论】:

  • 请注意,可以通过在 SQL 连接字符串中添加 Enlist=False 来忽略外部事务。
【解决方案2】:

这是“快照隔离”的自然行为。检查此链接: https://www.sqlshack.com/snapshot-isolation-in-sql-server/

“当另一个会话读取相同的数据时,返回读取事务开始时数据的提交版本。”

您必须终止会话才能让其他事务可以读取它。或将其更改为“已提交读”。

【讨论】:

  • 我知道快照隔离将读取最新完成事务的状态。我的问题是 DDL 事务没有在我预期的时候完成,所以我过早地开始发送通知 - 所以客户端请求(即快照隔离)读取了错误的数据。我的问题在于写入(而不是等待它完成),而不是读取。
  • 因为,在快照隔离中,当前版本的行被复制到 tempDb。如果有人想阅读它,数据库将返回旧数据。这样做是为了防止锁定。我的建议是尝试将隔离级别更改为已提交锁定未提交的行。但是在 ms sql server 中,有一种方法可以克服这个问题,在 select 语句的末尾添加 'with(nolock)'。
  • 实际上没有“太早”的制度。除非您结束会话,否则您将读取旧数据。
  • 服务器提交事务。然后它关闭与数据库的连接。然后它发出通知。然后客户端向服务器发出请求,服务器创建一个新会话(快照隔离)并读取数据。如果我读取提交的锁将确保后者读取等待。我确实需要使用快照隔离。我的问题是,IDbTransaction.Commit 在数据完全提交之前返回。
  • 该引用中没有任何内容表明您将在整个会话中读取相同的数据。 “返回读取事务开始时的数据版本” - 不是会话开始时。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-25
  • 2021-05-22
  • 1970-01-01
  • 2015-06-16
相关资源
最近更新 更多