102

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

  • 提交交易
  • 回滚事务
  • 什么都不做(这将导致事务在 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?
    }
}

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

4

12 回答 12

54

你承诺。时期。没有其他明智的选择。如果您开始交易,您应该关闭它。提交会释放您可能拥有的任何锁,并且对于 ReadUncommitted 或 Serializable 隔离级别同样明智。依赖隐式回滚——虽然在技术上可能是等效的——只是一种糟糕的形式。

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

于 2008-11-21T21:47:08.310 回答
33

如果您没有更改任何内容,那么您可以使用 COMMIT 或 ROLLBACK。任何一个都将释放您获得的任何读锁,并且由于您没有进行任何其他更改,因此它们将是等效的。

于 2008-11-21T19:22:32.377 回答
7

如果您开始一个事务,那么最佳实践是始终提交它。如果在你的 use(transaction) 块内抛出异常,事务将自动回滚。

于 2008-11-21T19:17:19.157 回答
3

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

于 2009-03-25T09:33:25.783 回答
3

考虑嵌套事务

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

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

一些数据库包装器可能会将内部事务中的回滚视为发生错误的标志,并回滚最外面事务中的所有内容,无论最外面的事务是提交还是回滚。

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

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

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

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

于 2017-05-05T14:04:55.803 回答
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 块移动到顶部。

于 2008-11-21T19:15:29.520 回答
1

如果将 SQL 放入存储过程并将其添加到查询上方:

set transaction isolation level read uncommitted

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

于 2008-11-21T21:45:37.517 回答
1

ROLLBACK 多用于发生错误或异常情况,COMMIT 用于成功完成。

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

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

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

如果 COMMIT 和 ROLLBACK 被正确用于只读事务,如果在某些时候添加了 DB 写入代码,它将继续按预期工作,例如用于缓存、审计或统计。

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

于 2015-09-08T10:05:23.433 回答
0

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

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

于 2008-11-21T19:14:01.567 回答
0

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

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

于 2017-11-13T06:44:26.217 回答
-3

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

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

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

SELECT * FROM SomeTable WITH (NOLOCK)

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

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

于 2008-11-21T19:15:20.957 回答
-3

在您的代码示例中,您有

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

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

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

但是,如果您的 SQL 语句没有更改任何内容,则您不能提交或回滚任何内容。

如果您正在更改数据,那么您可以更改隔离级别而无需显式启动事务......每个单独的 SQL 语句都隐含在事务中。显式启动事务只需要确保 2 个或更多语句在同一个事务中。

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

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

于 2008-11-21T19:19:25.457 回答