我应该提交还是回滚读取事务?

109

我有一个读取查询,我在事务内执行它,以便我可以指定隔离级别。一旦查询完成,我应该怎么做?

  • 提交事务
  • 回滚事务
  • 什么也不做(这将导致在使用块结束时回滚事务)

每种做法的影响是什么?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

编辑:问题不是是否应该使用事务或是否有其他设置事务级别的方式。问题是一个不修改任何内容的事务提交或回滚是否有任何区别。它是否会造成性能差异?它是否影响其他连接?是否存在其他差异?


1
你可能已经知道这个,但是根据你提供的例子,您可以通过简单执行查询获得相应的结果: SELECT * FROM SomeTable with NOLOCK - JasonTrue
@Stefan,看起来我们大多数人都想知道为什么你在只读操作上要进行事务处理。你能告诉我们你是否了解 NOLOCK,如果了解,为什么不采用该方法吗? - StingyJack
我知道NOLOCK,但这个系统不仅针对SQL Server操作不同的数据库,所以我尝试避免使用SQL Server特定的锁提示。这个问题更多是出于好奇而非其他原因,因为应用程序在上面的代码中正常工作。 - Stefan Moser
那么,我将删除sqlserver标签,因为它表示MSSqlServer是目标产品。 - StingyJack
@StingyJack - 你说得对,我不应该使用sqlserver标签。 - Stefan Moser
我来到这里是因为我对在事务中创建临时表、在选择/连接中使用,然后在结束时删除的操作非常感兴趣。由于我只是创建临时表以帮助连接多个ID,因此没有对数据库进行永久写入,但我想知道提交事务与回滚事务的性能影响。在我看来,回滚可能更昂贵,因为它可能必须“撤消”由创建/删除临时表可能导致的任何更改,而提交可能只是像“好的”。 - Triynko
12个回答

58

你需要提交。没有其他明智的选择。如果你开始了一个事务,你应该结束它。提交会释放任何可能存在的锁定,并且在ReadUncommitted 或 Serializable 隔离级别下同样合理。依赖隐式回滚 - 虽然从技术上讲等效 - 只是一种不好的形式。

如果这还没有说服你,试想一下下一个插入更新语句到你的代码中的人,他必须跟踪发生的隐式回滚并删除他的数据。


55
有一个明智的替代方案 - 回滚。 明确的回滚,也就是说,如果您不想修改任何内容,回滚可以确保撤消任何更改。 当然,本来就不应该有任何更改; 回滚可以保证这一点。 - Jonathan Leffler
9
假设我创建了一个临时表,用id填充它,将其与数据表连接以选择与id匹配的数据,然后删除临时表。我只是读取数据,并且不关心临时表会发生什么,因为它是临时的...但从性能的角度来看,回滚事务或提交事务哪个更昂贵?当涉及到临时表和读操作时,提交/回滚的影响是什么? - Triynko
4
直觉上,我猜撤销操作(ROLLBACK)会更加耗费资源。提交操作(COMMIT)是正常情况,而撤销操作则是异常情况。不过,除非是学术上的讨论,谁会关心呢?我相信你的应用程序还有1000个更好的优化点。如果你真的很好奇,可以在http://bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/head:/sql/transaction.cc找到MySQL事务处理代码。 - Mark Brackett
2
@Mark。这是一个优化点,因此我要对其进行优化。它被频繁调用(作为用户规模的问题,而不是应用程序设计),因此需要尽可能快。直觉上,人们会认为回滚更昂贵...但这取决于数据库如何提交事务和回滚事务,这就是我在问的原因。例如,回滚可以简单地丢弃内存中完成的工作,而提交可能会更改数据库统计信息或其他内容。 - Triynko
4
@Triynko - 唯一优化的方法是进行性能分析。这是一个非常简单的代码更改,如果您真的想要优化它,没有理由不对两种方法进行性能分析。请确保向我们更新结果! - Mark Brackett
显示剩余2条评论

37
如果您没有做任何更改,那么您可以使用COMMIT或ROLLBACK。两者都会释放您获取的所有读锁,并且由于您没有进行其他更改,它们是等效的。

3
谢谢让我知道它们是等价的。在我看来,这最能回答实际问题。 - chowey
如果我们在没有实际更新的情况下使用提交(commit),它会显示交易不活动。我在我的现场遇到了这个问题。 - Muhammad Omer Aslam
1
@chowey,我认为这完全没有回答问题。问题的主要重点是从性能角度来看,ROLLBACK或COMMIT是否具有优势。它们在功能上等效的事实是问题的前提条件,而不是问题的一部分。因此,确认它们在功能上等效完全忽略了问题的要点。 - Jaredo Mills

7

如果你开始一个事务,最好的做法就是提交它。如果在use(transaction)块内部抛出异常,则事务将自动回滚。


5
考虑使用嵌套事务
大多数RDBMS不支持嵌套事务,或者只是以非常有限的方式模拟它们。
例如,在MS SQL Server中,内部事务(这不是一个真正的事务,MS SQL Server只计算事务级别!)中的回滚将回滚在最外层事务(即真正的事务)中发生的所有内容。
一些数据库包装器可能会将内部事务中的回滚视为错误的标志,并回滚最外层事务中的所有内容,无论最外层事务是提交还是回滚。
因此,当您无法排除组件被某些软件模块使用时,COMMIT是安全的方法。
请注意,这是问题的一般答案。代码示例通过打开新的数据库连接巧妙地解决了外部事务的问题。
关于性能:根据隔离级别,SELECT可能需要不同程度的锁定和临时数据(快照)。当事务关闭时,这将得到清除。无论是通过COMMIT还是ROLLBACK完成,都没有关系。在CPU时间方面可能存在微小的差异 - COMMIT可能比ROLLBACK更快速分析(少两个字符)和其他小差异。显然,这仅适用于只读操作!
完全没有要求的是:另一个程序员可能会认为ROLLBACK意味着错误条件。

4
在出现错误或异常情况时,通常使用ROLLBACK,而在成功完成的情况下使用COMMIT。即使是只读事务,在关闭事务时也应该使用COMMIT(表示成功)和ROLLBACK(表示失败)。实际上,这很重要,可以保证一致性和未来可扩展性。
只读事务可能会以多种方式逻辑上“失败”,例如:
- 查询未返回预期的一行数据 - 存储过程引发异常 - 获取的数据被发现不一致 - 用户中止事务因为时间太长 - 死锁或超时
如果只读事务正确使用COMMIT和ROLLBACK,则在某个时刻添加DB写入代码(例如缓存、审计或统计)后,它将继续按预期工作。
隐式ROLLBACK仅应用于“致命错误”情况,例如应用程序崩溃或退出时,网络故障,电源故障等。

4

在我看来,在事务中包装只读查询是有意义的,特别是在Java中。您可以告诉事务是“只读”的,这样JDBC驱动程序可以考虑优化查询(但不必这样做,因此没有人会阻止您发出INSERT)。例如,Oracle驱动程序将完全避免在标记为只读的事务中进行表锁定,这在重读驱动应用程序上获得了很多性能提升。


2

顺便提一下,您也可以这样编写代码:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

如果你稍微调整一下结构,也许你可以把使用 IDataReader 的 using 代码块移到顶部。


1
如果您将SQL放入存储过程中并在查询上方添加以下内容:
set transaction isolation level read uncommitted

那么在C#代码中,您就不必费心了。在存储过程中设置事务隔离级别并不会导致该设置应用于该连接的所有未来使用(这是您需要担心其他设置的原因,因为连接是池化的)。在存储过程结束时,它只会回到连接初始化的状态。


0

考虑到读取操作不会改变状态,我什么也不会做。执行提交操作将什么也不会做,除了浪费一个周期将请求发送到数据库。您还没有执行更改状态的操作。回滚同样如此。

但是,您应该确保清理对象并关闭与数据库的连接。如果重复调用此代码而不关闭连接,可能会导致问题。


3
根据隔离级别,SELECT语句可以获取锁定并阻塞其他事务。 - Graeme Perrow
连接将在使用块结束时关闭 - 这就是它存在的原因。但是很好的一点是网络流量可能是方程式中最慢的部分。 - Joel Coehoorn
1
无论如何交易都会被提交或者回滚,因此最佳实践是如果成功了就始终发出提交。 - Neil Barnwell

0
如果您将AutoCommit设置为false,则是的。
在使用JDBC(Postgresql驱动程序)进行实验时,我发现如果选择查询中断(因为超时),则除非回滚,否则无法启动新的选择查询。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接