如果你不提交事务到数据库(比如SQL Server),会发生什么?

144

假设我有一个查询:

begin tran
-- some other sql code

然后我忘记提交或回滚。

如果另一个客户端尝试执行查询,会发生什么?

10个回答

206
只要您不执行“COMMIT”或“ROLLBACK”事务,它仍然在“运行”,并且有可能持有锁定。
如果您的客户端(应用程序或用户)在提交之前关闭与数据库的连接,则任何仍在运行的事务都将被回滚并终止。

2
嗯,好的,我明白了,这是在创建某种锁。我不确定关闭连接是否真的能让我摆脱这种状态。问题是当我尝试提交时出现了错误。现在我关闭了连接,一切都正常了。 - Charbel
18
附注:如果使用管理工具,关闭查询窗口将会关闭连接。 - Joe Phillips
3
可以的。 - marc_s
4
请记住,SQL Server Management Studio 默认情况下在关闭查询窗口/连接时会自动提交。 - Nuno
2
每个人都应该注意这个回复中的“可能持有锁”的部分。 - fiorentinoing
显示剩余5条评论

49

你可以亲自尝试一下,这会帮助你更加了解它是如何工作的。

在管理工具中打开两个窗口(标签),每一个窗口都有自己连接到 SQL 的方式。

现在,在一个窗口中开始一个事务,并执行一些操作,比如插入/更新/删除,但还没有提交。然后,在另一个窗口中,您可以查看从事务外部看数据库的情况。根据隔离级别,直到第一个窗口提交之前,表可能会被锁定,或者你可能(不)看到其他事务已经做了什么等等。

尝试使用不同的隔离级别和无锁提示来看看它们如何影响结果。

还要看看在事务中发生错误时会发生什么。

非常重要的是要理解所有这些东西是如何工作的,否则您会经常被 SQL 所困扰。

玩得开心!GJ。


1
好的,但在提交之前,事务是否至少会被写入日志?例如,假设我想启动一个事务运行插入命令并在执行提交之前“做其他事情”。我的插入命令会被写入日志吗?这样,如果服务器在执行提交之前崩溃...它可以回到原来的位置,我稍后可以随时发出提交(无论何时我完成“其他事情”)。 - user1870400
1
@user1870400 看起来显然这个用户会给出的答案是……试一下。 - Daniel

23

事务旨在完全运行或不运行。 完成交易的唯一方法是提交,任何其他方式都将导致回滚。

因此,如果您开始但未提交,则在连接关闭时它将被回滚(因为事务已被中断而没有标记为完成)。


1
这就是应该的方式,但情况并非总是如此。 - FalcoGer
比如mySQL的MyISAM,它确实不支持事务。 - Piskvor left the building

4

7
事务的行为不取决于隔离级别,但它们可能引起的锁数量会有所不同。 - marc_s
我非常确定连接可以读取的数据肯定取决于隔离级别。如果你设置了未提交读(READ UNCOMMITTED)作为隔离级别,则可以读取尚未提交的数据,但可能会在某些时候被回滚,但这可以确保没有锁定。如果将已提交读(READ COMMITTED)作为隔离级别,则无法读取未提交的行 - 第二个客户端将挂起,除非使用快照(SNAPSHOT)。 - Xhalent

4

当您打开一个事务时,它本身并不会被锁定。但是,如果在该事务中执行一些查询,根据隔离级别,某些行、表或页面将被锁定,因此将影响其他事务尝试从中访问它们的查询。


1
任何未提交的事务都会使服务器处于锁定状态,其他查询无法在服务器上执行。您需要回滚事务或提交它。关闭SSMS也将终止事务,从而允许其他查询执行。

在我的情况下,我只关闭了包含已执行事务查询的选项卡(文件),然后SSMS问我是否要提交事务。 - Lazar Đorđević

1

事务示例

开始事务 tt

您的 SQL 语句

如果发生错误,则回滚事务 tt;否则,提交事务 tt。

只要未执行 commit tran tt,数据就不会被更改。


1
请注意,在MS SQL中命名事务不仅是不必要的,而且可能会给人一种虚假的控制感。例如,“BEGIN TRAN X ... BEGIN TRAN Y ... ROLLBACK Y”是无效的。请参见https://dev59.com/8HM_5IYBdhLWcg3wrFMt。 - user565869

0
除了可能引起的锁定问题外,您还会发现事务日志开始增长,因为它们无法截断到活动事务的最小LSN。如果您正在使用快照隔离,则由于类似的原因,tempdb中的版本存储将增长。
您可以使用dbcc opentran查看最旧的打开事务的详细信息。

0

我真的忘记提交事务了。我有一个像下面代码一样的查询。

这个存储过程是由.Net调用的。当我在.Net应用程序中测试函数时,异常将被捕获在.Net应用程序中。

异常消息如下:

执行后的事务计数表明BEGIN和COMMIT语句不匹配。之前的计数=0,当前的计数=1。

当我意识到错误时,我尝试了很多次,在.Net应用程序和SQL Server Management Studio(2018)中都尝试了。(在SSMS中,输出语句将在“结果”选项卡中成功输出结果,但在“消息”选项卡中显示错误消息。)

然后我发现在此事务中使用的表被锁定了。当我只选择前1000个而没有使用order desc时,它可以选择结果。但是当我选择前1000个并使用order desc时,它将运行很长时间。

当我关闭.Net应用程序时,事务未提交(基于事务中未更改的数据)。

当我关闭执行伪造提交查询的EXEC ...选项卡时,SSMS将弹出一个警告窗口:

有未提交的事务。您是否要提交这些事务?

我已经测试了两个选项。

如果我点击,那么这些事务将被提交。

如果我点击,那么这些事务将不会被提交。

在关闭选项卡后,我的锁定表将被释放,然后我就可以成功查询。

begin try 
    -- some process
    begin transaction
    update ...
    output ...
    

    insert ...

    -- I missing this commit statement below
    commit transaction  
end try
begin catch 
    if (xact_state()) = -1
    begin 
        rollback transaction;
        ;throw
    end;

    -- this statement I want to compare to 1, but mistake write to -1, but since the throw statement let the mistake can't be triggerd
    if (xact_state()) = 1
    begin
        commit transaction;
    end;
end catch;

-4
行为未定义,因此您必须明确设置提交或回滚:

http://docs.oracle.com/cd/B10500_01/java.920/a96654/basic.htm#1003303

如果禁用了自动提交模式,并且在关闭连接之前没有显式地提交或回滚您最后的更改,则会执行隐式的 COMMIT 操作。Hsqldb 将进行回滚。
con.setAutoCommit(false);
stmt.executeUpdate("insert into USER values ('" +  insertedUserId + "','Anton','Alaf')");
con.close();

结果是

2011-11-14 14:20:22,519 主要的 信息 [SqlAutoCommitExample:55] [自动提交已启用 = false] 2011-11-14 14:20:22,546 主要的 信息 [SqlAutoCommitExample:65] [在数据库中找到0#用户]


2
这可能对Oracle(我不清楚)是正确的,但提问者在询问关于MS-SQL的事情。 - PaulG
第一条引用适用于JDBC驱动程序,而不是服务器。 - djechlin

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