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

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

  • 提交交易
  • 回滚事务
  • 什么都不做(这将导致事务在使用块的末尾回滚)

什么是做每个的影响?

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? } } 

编辑:问题是,如果一个交易应该使用,或者如果有其他的方式来设置交易水平。 现在的问题是,如果一个不会修改任何内容的事务被提交或回滚,这是否有什么区别。 是否有性能差异? 它会影响其他连接吗? 还有其他的区别?

你承诺。 期。 没有其他明智的select。 如果你开始了一个交易,你应该closures它。 承诺释放您可能拥有的任何锁,并且对ReadUncommitted或Serializable隔离级别同样明智。 依靠隐含的回滚 – 虽然也许在技术上相当于 – 只是不好的forms。

如果这不能说服你,那就想象下一个在你的代码中插入一条更新语句的人,并且必须跟踪发生的隐式回滚并删除他的数据。

如果你没有改变任何东西,那么你可以使用COMMIT或ROLLBACK。 任何一个人都会释放你已经获得的读锁,因为你没有做任何其他的改变,他们将是等同的。

如果你开始一个交易,那么最好的做法是总是提交。 如果在您的使用(交易)块内引发exception,交易将自动回滚。

恕我直言,它可以有意义的包装只读查询交易中(特别是在Java中),你可以告诉事务是“只读”,这反过来JDBC驱动程序可以考虑优化查询(但不必,所以没有人将阻止你发布INSERT )。 例如,Oracle驱动程序将完全避免对标记为只读的事务中的查询进行表locking,这会在大量读取驱动的应用程序中获得大量性能。

只是一个侧面说明,但你也可以写这样的代码:

 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? } 

如果你重新构build一些东西,你也许可以把IDataReader的使用块移到顶端。

如果将SQL放入存储过程并在查询上添加以上内容:

 set transaction isolation level read uncommitted 

那么你不必跳过C#代码中的任何环节。 在存储过程中设置事务隔离级别不会导致该设置应用于该连接的所有将来使用(这是因为连接已被池化,所以您必须担心其他设置)。 在存储过程结束时,它会返回到连接初始化的任何地方。

ROLLBACK主要用于出现错误或特殊情况,并在成功完成的情况下提交。

我们应该使用COMMIT(成功)和ROLLBACK(失败)来closures事务,即使在只读事务中,它似乎并不重要。 事实上,为了保持一致性和面向未来,这很重要。

只读事务可以在很多方面在逻辑上“失败”,例如:

  • 查询不会像预期的那样完全返回一行
  • 存储过程引发exception
  • 发现获取的数据不一致
  • 用户中止交易,因为它需要太长时间
  • 死锁或超时

如果COMMIT和ROLLBACK正确地用于只读事务,它将继续按预期工作,如果在某个时候添加了数据库写入代码,例如用于caching,审计或统计。

隐式ROLLBACK只能用于“致命错误”的情况,当应用程序崩溃或退出时出现不可恢复的错误,networking故障,电源故障等。

考虑嵌套事务

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

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

某些数据库包装器可能会考虑在内部事务中进行回滚,作为出现错误的标志,并且回滚最外层事务中的所有内容,而不pipe最外层事务是否已提交或回退。

所以当你不能排除你的组件被一些软件模块使用时,COMMIT是安全的。

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

关于性能:根据隔离级别,SELECT可能需要不同程度的LOCK和临时数据(快照)。 交易closures时,这个会被清理干净。 这是否通过COMMIT或ROLLBACK完成并不重要。 CPU花费的时间可能会有微不足道的差别 – 一个COMMIT的parsing速度可能比一个ROLLBACK(less两个字符)和其他细微差别要快。 显然,这只适用于只读操作!

完全不要求:另一个可能读取代码的程序员可能会认为ROLLBACK意味着错误条件。

鉴于一个阅读不改变状态,我什么也不做。 执行提交将不会执行任何操作,只是浪费一个周期来将请求发送到数据库。 您尚未执行更改状态的操作。 同样的回滚。

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

如果您将“自动提交”设置为false,则“是”。

在使用JDBC(Postgresql驱动程序)的exp中,我发现如果select查询中断(由于超时),则除非回滚,否则不能启动新的select查询。

在你的代码示例中,你有

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

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

如果没有,就没有“读取”事务这样的事情……只有插入,更新和删除语句(可以改变数据的语句)的变化才在事务中…你所说的是SQL由于影响该数据的其他事务,服务器将读取您正在读取的数据。 这些锁的级别取决于SQL Server隔离级别。

但是如果你的SQL语句没有改变任何东西,你就不能提交,或者返回任何东西。

如果您正在更改数据,则可以更改隔离级别而不显式启动转换…每个单独的SQL语句都隐含在事务中。 明确地启动一个事务只是为了确保两个或更多的语句在同一个事务中。

如果你想要做的只是设置事务隔离级别,那么只需要设置一个命令的CommandText为“Set Transaction Isolation level Repeatable Read”(或者你想要的任何级别),将CommandType设置为CommandType.Text并执行该命令。 (你可以使用Command.ExecuteNonQuery())

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

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

@Joel – 我的问题会更好措辞为“为什么使用读取查询事务?

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

 SELECT * FROM SomeTable WITH (NOLOCK) 

编辑@评论3:既然你有问题标签中的“sqlserver”,我认为MSSQLServer是目标产品。 现在已经澄清了这一点,我编辑了标签以删除特定的产品参考。

我仍然不确定你为什么想在一个阅读操作上进行交易。