我应该提交或回滚已读事务吗?


95

我有一个事务中执行的读取查询,以便可以指定隔离级别。查询完成后,该怎么办?

  • 提交交易
  • 回滚交易
  • 不执行任何操作(这将导致事务在using块的末尾回滚)

进行每个操作有什么含义?

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

Answers:


51

你犯了。期。没有其他明智的选择。如果您开始交易,则应将其关闭。提交会释放您可能拥有的所有锁,并且对于ReadUncommitted或Serializable隔离级别同样有意义。依靠隐式回滚-也许在技术上是等效的-只是一种糟糕的形式。

如果那没有使您信服,请想象下一个在代码中间插入一条update语句,并且必须跟踪发生的隐式回滚并删除其数据的家伙。


44
有一个明智的选择-回滚。即显式回滚。如果您不想更改任何内容,则回滚可确保所有操作都被撤消。当然,不应该进行任何更改。回滚保证了这一点。
乔纳森·莱夫勒

2
不同的DBMS可以具有不同的“隐式事务完成”语义。IBM Informix(我相信DB2)会进行隐式回滚。据传,Oracle进行了隐式提交。我更喜欢隐式回滚。
乔纳森·莱夫勒

8
假设我创建了一个临时表,用ID填充它,将其与数据表连接以选择ID附带的数据,然后删除该临时表。我实际上只是在读取数据,我不在乎临时表会发生什么,因为它是临时的……但是从性能的角度来看,回滚事务或提交它会更昂贵吗?当只涉及临时表和读取操作时,提交/回滚有什么影响?
Triynko 2010年

4
@Triynko-直观地讲,我想ROLLBACK会更昂贵。COMMIT是正常的用例,而ROLLBACK是例外的用例。但是,除了学术上,谁在乎?我确定您的应用程序有1000个更好的优化点。如果您真的很好奇,可以在bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…上
Mark Brackett 2010年

3
@Triynko- 优化的唯一方法是分析。如此简单的代码更改,如果您真的想对其进行优化,则没有理由不对这两种方法进行概要分析。确保以结果更新我们!
Mark Brackett 2010年

28

如果您没有进行任何更改,则可以使用COMMIT或ROLLBACK。任一者都将释放您已获取的所有读锁,并且由于您未进行任何其他更改,因此它们是等效的。


2
感谢您让我知道它们是等效的。我认为,这最好地回答了实际问题。
chowey

如果我们使用没有实际更新的提交,它将使事务处于非活动状态。我只是在我的实时网站上面对它
穆罕默德·奥默·阿斯兰

6

如果您开始交易,则最佳实践始终是提交交易。如果在use(transaction)块中引发异常,则事务将自动回滚。


3

恕我直言,将只读查询包装在事务中非常有意义,因为(尤其是在Java中)您可以告诉事务是“只读的”,这反过来JDBC驱动程序可以考虑优化查询(但不必这样做,所以没有人这样做)仍会阻止您发行INSERT)。例如,Oracle驱动程序将完全避免在标记为只读的事务中查询上的表锁定,从而在大量读取驱动的应用程序上获得很多性能。


3

考虑嵌套事务

大多数RDBMS不支持嵌套事务,或尝试以非常有限的方式模拟它们。

例如,在MS SQL Server中,内部事务中的回滚(这不是真正的事务,MS SQL Server只是计算事务级别!)将回滚最外层事务(即真正的事务)中发生的一切。

一些数据库包装程序可能会将内部事务中的回滚视为已发生错误的标志,并回退最外层事务中的所有内容,而不管最外层事务是已提交还是已回滚。

因此,当您不能排除某些软件模块使用了组件时,COMMIT是安全的方法。

请注意,这是对该问题的一般回答。该代码示例通过打开新的数据库连接巧妙地解决了外部事务的问题。

关于性能:根据隔离级别,SELECT可能需要不同程度的LOCK和临时数据(快照)。当事务关闭时,将清除此错误。这是通过COMMIT还是ROLLBACK完成并不重要。花费的CPU时间可能差别不大-COMMIT的解析速度可能比ROLLBACK(少两个字符)和其他细微差别要快。显然,这仅适用于只读操作!

完全没有要求:另一个可能会阅读代码的程序员可能会假定ROLLBACK暗示错误情况。


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#代码中的任何障碍。在存储过程中设置事务隔离级别不会导致该设置应用于该连接的所有将来使用(由于连接是池化的,因此您需要担心其他设置)。在存储过程结束时,它只返回到初始化连接所用的任何内容。


1

ROLLBACK通常用于出现错误或异常情况的情况,而COMMIT用于成功完成的情况。

我们应该使用COMMIT(对于成功)和ROLLBACK(对于失败)关闭事务,即使在只读事务似乎无关紧要的情况下也是如此。实际上,这对于一致性和面向未来至关重要。

只读事务可以通过多种方式在逻辑上“失败”,例如:

  • 查询未按预期返回准确的一行
  • 存储过程引发异常
  • 发现获取的数据不一致
  • 用户中止事务,因为它花费的时间太长
  • 死锁或超时

如果将COMMIT和ROLLBACK正确用于只读事务,则在某个时候添加DB写代码(例如用于缓存,审计或统计)的情况下,它将继续按预期工作。

隐式ROLLBACK仅应在“致命错误”情况下使用,当应用程序崩溃或退出并出现不可恢复的错误,网络故障,电源故障等时。


0

鉴于READ不会更改状态,所以我什么也不会做。执行提交将无济于事,除了浪费一个周期将请求发送到数据库。您尚未执行更改状态的操作。对于回滚也是如此。

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


3
根据隔离级别,选定的CAN可以获取将阻止其他事务的锁。
Graeme Perrow,

连接将在using块的末尾关闭,这就是它的用途。但是,好的一点是,网络流量可能是方程式中最慢的部分。
Joel Coehoorn

1
事务将以一种或另一种方式提交或回滚,因此最佳实践是始终成功执行提交。
尼尔·巴恩韦尔

0

如果将AutoCommit设置为false,则为YES。

在JDBC(Postgresql驱动程序)的实验中,我发现如果选择查询中断(由于超时),那么除非回滚,否则您将无法启动新的选择查询。


-2

在您的代码示例中

  1. //做一些有用的事情

    您是否正在执行更改数据的SQL语句?

如果不是,则没有“读取”事务之类的东西...事务中仅包含插入,更新和删除语句(可以更改数据的语句)中的更改...您在说的是SQL的锁由于其他事务会影响您的数据,因此服务器将您正在读取的数据作为数据。这些锁的级别取决于SQL Server隔离级别。

但是,如果您的SQL语句未更改任何内容,则无法提交或回滚任何内容。

如果要更改数据,则可以在不显式启动转换的情况下更改隔离级别...每个SQL语句都隐式包含在事务中。显式启动事务仅需要确保同一事务中有2条或更多条语句。

如果您只想设置事务隔离级别,则只需将命令的CommandText设置为“设置事务隔离级别可重复读取”(或所需的任何级别),将CommandType设置为CommandType.Text,然后执行命令。(您可以使用Command.ExecuteNonQuery())

注意:如果您要执行多个读取语句,并希望它们全部“看到”与第一个相同的数据库状态,则需要将隔离级别设置为“顶部可重复读取”或“可序列化...”。


//做一些有用的操作不会更改任何数据,只需读取即可。我要做的就是指定查询的隔离级别。
Stefan Moser,

然后,您可以执行此操作而无需从客户端显式启动事务……只需执行sql字符串“设置事务隔离级别ReadUncommitted”,“ ... Read Committed”,“ ... RepeatableRead”,“ ... Snapshot”或“ ...可序列化”“设置提交的隔离级别已提交”
Charles Bretana

3
即使您只是阅读,交易仍然很重要。如果要执行多个读取操作,则在事务内进行读取操作可确保一致性。没有一个人就不会做。
MarkR

是的,抱歉,您是对的,如果将“隔离级别”设置为“可重复读取”或更高,则至少是这样。
查尔斯·布雷塔纳

-3

您是否需要阻止其他人读取相同的数据?为什么要使用交易?

@Joel-我的问题最好用“为什么在读取的查询中使用事务?”来表达。

@Stefan-如果要使用AdHoc SQL而不是存储的proc,则只需在查询中的表后面添加WITH(NOLOCK)。这样,您就不会在应用程序和数据库中产生事务的开销(尽管很小)。

SELECT * FROM SomeTable WITH (NOLOCK)

编辑@注释3:由于问题标记中包含“ sqlserver”,因此我假设MSSQLServer是目标产品。现在已经弄清楚了这一点,我已经编辑了标签以删除特定的产品参考。

我仍然不确定为什么首先要在读取操作上进行交易。


1
到设置的隔离级别一次。您可以使用事务来实际减少查询的锁定量。
Joel Coehoorn

1
我正在使用事务,以便可以使用较低的隔离级别并减少锁定。
Stefan Moser,

@StingyJack-此代码可以针对许多不同的数据库执行,因此NOLOCK不是一个选择。
Stefan Moser,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.