English 中文(简体)
我应该提交还是回滚一个读取事务?
原标题:
  • 时间:2008-11-21 19:10:27
  •  标签:

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

  • Commit the transaction
  • Rollback the transaction
  • Do nothing (which will cause the transaction to be rolled back at the end of the using block)

做每件事情的影响是什么?

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

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

最佳回答

你必须提交。没有其他明智选择。如果你开始了一项交易,你应该关闭它。提交会释放你可能拥有的任何锁,并且在ReadUncommitted或Serializable隔离级别下同样是合理的。依赖隐式回滚——尽管在技术上可能等效——只是不好的形式。

如果这还没有说服你,那就想象一下下一个插入更新语句到你的代码中的人,他不得不追踪隐式回滚并删除他的数据。

问题回答

如果您没有改变任何东西,那么您可以使用COMMIT或ROLLBACK。两者都将释放您获取的任何读锁定,因为您没有进行任何其他更改,它们将是等效的。

如果您开始一笔交易,最好的做法是始终提交它。如果在您的 use(transaction) 块内抛出异常,交易将自动回滚。

考虑嵌套事务

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

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

一些数据库包装器可能认为在内部事务中回滚是发生错误的迹象,并回滚最外层事务中的所有内容,而不管最外层事务是提交还是回滚。

当您不能排除您的组件被某个软件模块使用时,COMMIT是一种安全的方式。

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

关于性能:根据隔离级别,SELECT可能需要不同程度的LOCK和临时数据(快照)。在事务关闭时进行清除。无论是通过提交还是回滚进行清除都没有关系。在CPU时间花费方面可能会有微不足道的差异 - 提交比回滚可能更快解析(少两个字符)和其他轻微的差异。显然,这只适用于只读操作!

完全不需要的:另一个程序员可能会假设ROLLBACK表示有错误情况,而实际上并非如此。

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

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

我们应该在提交成功的情况下使用 COMMIT,出现失败的情况下使用 ROLLBACK 来完成交易,即使是只读交易,我们也应该这样做。实际上这很重要,可以确保一致性并保障未来。

一个只读事务在逻辑上可以以许多方式“失败”,例如:

  • a query does not return exactly one row as expected
  • a stored procedure raises an exception
  • data fetched is found to be inconsistent
  • user aborts the transaction because it s taking too long
  • deadlock or timeout

如果在只读事务中正确使用 COMMIT 和 ROLLBACK,那么如果在某个时间点添加了 DB 写入代码(例如用于缓存、审计或统计),它将继续按预期工作。

隐式回滚只应用于“致命错误”情况,如应用程序崩溃或因不可恢复的错误退出,网络故障、电源故障等。

只是一点小提示,你也可以这样写代码:

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的块移到顶部。

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

set transaction isolation level read uncommitted

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

鉴于读取操作不会改变状态,我将不做任何事情。执行提交操作也不会产生任何作用,除了浪费一个周期向数据库发送请求。您还没有执行更改状态的操作。回滚操作同样如此。

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

如果将AutoCommit设为false,那么是的。

在 JDBC(Postgresql 驱动)的实验中,我发现如果选择查询中断(因为超时),则无法启动新的选择查询,除非您回滚。





相关问题
热门标签