我从事T-SQL的开发已经有几年了,并且一直在不断深入研究,继续学习有关该语言各个方面的所有知识。我最近开始在一家新公司工作,收到了我认为关于交易的奇怪建议。永远不要使用它们。而是,使用一种模拟交易的解决方法。这来自于我们的DBA,该DBA在一个数据库中处理大量事务,并随后进行大量阻塞。我主要工作的数据库不受此问题的困扰,并且我看到过去已经使用过事务。不用一个就可以逃脱,一定要做到。但是在很多情况下,每个语句必须成功执行。如果失败了,那么所有人都必须提交。

我一直将交易范围保持在尽可能窄的范围内,始终与SET XACT_ABORT ON结合使用,并且始终在TRY / CATCH中使用。

示例:

 CREATE SCHEMA someschema;
GO


CREATE TABLE someschema.tableA
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColA VARCHAR(10) NOT NULL
);
GO

CREATE TABLE someschema.tableB
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColB VARCHAR(10) NOT NULL
); 
GO


CREATE PROCEDURE someschema.ProcedureName @ColA VARCHAR(10), 
                                          @ColB VARCHAR(10)
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN
BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);

--Implement error
    SELECT 1/0 

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@trancount > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END;
    THROW;
    RETURN;
END CATCH;
END;
GO

 


这是他们建议我做的。

 GO



CREATE PROCEDURE someschema.ProcedureNameNoTransaction @ColA VARCHAR(10), 
                                                       @ColB VARCHAR(10)
AS
SET NOCOUNT ON;
BEGIN
BEGIN TRY
    DECLARE @tableAid INT;
    DECLARE @tableBid INT;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);
    SET @tableAid = SCOPE_IDENTITY();

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);
    SET @tableBid = SCOPE_IDENTITY();

--Implement error
    SELECT 1/0 

END TRY
BEGIN CATCH
    DELETE FROM someschema.tableA
    WHERE id = @tableAid;

    DELETE FROM someschema.tableB
    WHERE id = @tableBid;

    THROW;

    RETURN;
END CATCH;
END;
GO
 


我对社区的提问如下。

我对交易的了解以及所提出的解决方案的看法是,不,这不是可行的解决方案,会带来很多故障。

在建议的解决方法中,我看到发生了四个隐式事务。在try中插入两个,然后在catch中进行两个删除操作。它会“撤消”插入,但不会回滚任何内容,因此实际上不会回滚任何内容。

这是一个非常基本的示例,可以证明他们所建议的概念。我一直在执行的一些实际存储过程使它们变得冗长且难以管理,因为在此示例中,“回滚”多个结果集和两个参数值变得​​非常复杂,如您所想。由于“回滚”现在是手动完成的,因此有机会错过一些真实的事情。

我认为存在的另一个问题是超时或断开连接。这还会回滚吗?这就是我对为什么应使用SET XACT_ABORT ON的理解,以便在这种情况下事务将回滚。

感谢您的提前反馈!

评论

不符合其既定目的的评论已删除,或移至“社区Wiki”答案。

#1 楼

您不能在SQL Server(以及任何其他适当的RDBMS)中使用事务。在没有显式事务边界(begin transaction ... commit)的情况下,每个SQL语句都会启动一个新事务,该事务将在语句完成(或失败)后隐式提交(或回滚)。

将自己描述为您的“ DBA”的人建议的交易模拟无法确保事务处理的四个必要属性中的三个,因为它只能解决“软”错误并且不能处理“硬”错误(例如,网络断开连接,断电,磁盘故障等)的原因。如果在伪交易的中间某处发生“硬”错误,则更改将是非原子的。
一致性:失败。从上面可以看出,由于“硬”错误,您的数据将处于不一致状态。
隔离:失败。并发伪事务可能会在完成之前更改某些由伪事务修改的数据。
耐久性:成功。您所做的更改将是持久的,数据库服务器将确保该更改;这是您的同事唯一无法解决的问题。

锁是一种广泛使用的,凭经验获得成功的方法,可以确保各种交易或RDBMS的ACIDity(以本网站为例)。我发现随机DBA不可能比在过去50年来建立了一些有趣的数据库系统的数百(可能是数千)计算机科学家和工程师能够更好地解决并发问题。 60年? (我意识到这有点像“呼吁权威”的论点是虚假的,但无论如何我都会坚持下去。)

最后,如果可以的话,请忽略“ DBA”的建议如果您有精神,请返回此处,如果出现并发问题,请返回此处。

#2 楼

有些错误非常严重,以至于永远不会输入CATCH块。根据文档

,严重性为20或更高的错误将停止会话的SQL Server数据库引擎任务处理。如果发生严重性为20或更高的错误,并且数据库连接没有中断,则TRY ... CATCH将处理该错误。
注意事项,例如客户端中断请求或客户端连接断开。
当系统管理员使用KILL语句结束会话时。
...
编译错误(例如语法错误)会阻止批处理运行。
发生错误..

其中许多很容易通过动态SQL生成。如您所显示的撤消语句不会保护您的数据免受此类错误的影响。

评论


是的,如果没有其他事情,那么在执行代码时垂死的客户端将构成一个错误“严重到根本无法输入CATCH块”。无论您多么信任软件(不仅是您自己的代码,而且还涉及到所有软件堆栈的每个部分),总有可能发生硬件故障(再次可能在链中的任何地方)使您随时冷落。牢记这一点可以很好地抵御导致这种“变通办法”的善意思考。

– dgould
19-09-13在16:31



另外,您可能是死锁的受害者。您的CATCH块可以运行,但如果尝试写入数据库则抛出该块。

–约书亚
19-09-13在20:38

#3 楼

i-one:建议您使用的变通办法可以(至少)违反ACID的“ A”。例如,如果SP正在由远程客户端执行,并且连接中断,则可能会发生部分“提交” /“回滚”,因为服务器可以终止两次插入/删除之间的会话(并在SP到达末尾之前中止SP执行) 。


作为事务的可行替代方法,这有意义吗?查询超时的情况,因为客户端API取消了该批次。如果没有事务,则CATCH不能回滚除当前语句以外的任何内容。

tibor-karaszi:您有4个事务,这意味着需要更多记录到事务日志文件中。请记住,到目前为止,每个事务都需要同步写入日志记录,即,在使用许多事务时,从那个方面来看,您的性能也会下降。


此外,他们试图手动实现的实际上是穷人的乐观并发。他们应该做的是使用已经内置在SQL Server中的一些世界上最好的乐观并发。转到上面的隔离点。他们极有可能需要从当前使用的任何悲观并发隔离级别切换到乐观并发隔离级别SET XACT_ABORT ONSNAPSHOT之一。这些将有效地执行其手册代码中的相同操作,只是正确执行。

ross-presser:如果您运行的过程非常漫长,例如今天和下周发生了某些事情,则必须采取后续措施;如果下周的事情失败了,那么今天的事情将追溯失败-您可能希望调查sagas。严格来说,这是在数据库外部,因为它需要服务总线。

#4 楼

错误的主意代码将使修复工作更加昂贵。

如果使用显式事务(回滚/提交)存在阻塞问题,请将您的DBA指向互联网,以寻求一些好主意问题。

这是一种缓解阻塞的方法:https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions


索引减少了在表/页面中查找行/行集所必须进行的查找次数。通常,它们被视为减少SELECT *查询执行时间的一种方法,也是正确的。
不认为它们适用于涉及大量UPDATES的表。实际上,在这些情况下发现INDEXES是不利的,因为它们会增加完成UPDATE查询所需的时间。

并非总是如此。深入研究UPDATE语句的执行,我们发现它也涉及先执行SELECT语句。这是一种特殊且经常被看到的情况,在这种情况下,查询将更新互斥的行集。 />

#5 楼

虚假交易策略之所以危险,是因为它允许并发问题,而这些问题是交易特别防止的。请考虑在第二个示例中,语句之间的任何数据都可能会发生更改。

不保证假交易删除运行或成功。如果在假交易期间关闭数据库服务器,则某些效果(但不是全部)将保留。

此策略也不能保证成功地实现事务回滚。 br />
如果严格的事务并发导致阻塞,那么有很多解决方案,甚至是降低保护级别的解决方案...这些都是解决问题的正确方法。

您的DBA正在提供一种解决方案,如果只有一个数据库用户,该解决方案可能会正常工作,但绝对不适合任何严重使用。

#6 楼

这不是编程问题,而是人际关系/沟通不畅的问题。您的“ DBA”很可能担心锁,而不是事务。

其他答案已经解释了为什么必须使用事务……我的意思是RDBMS就是这样做的,没有正确使用事务就没有数据完整性,所以我将集中于如何解决真正的问题,即:找出为什么您的“ DBA”对交易产生过敏并说服他改变主意。

我认为家伙正在把“错误代码导致糟糕的性能的特定情况”与“所有事务都是错误的”混为一谈。我不希望有能力的DBA犯这个错误,所以这真的很奇怪。也许他对一些可怕的代码有过很糟糕的经历?

考虑这样的情况:锁(或几个锁),这意味着其他事务到达同一行将必须等待。如果锁持有时间很长,尤其是如果许多其他事务想要锁定同一行,那么这确实会损害性能。

您可以做的就是问他为什么他如此好奇错误的想法,不使用事务,什么类型的查询有问题,等等。然后尝试说服他,您一定会避免类似的不良情况,您将监视锁的使用和性能,向他保证放心,等等。

他告诉你的是“不要碰螺丝刀!”因此,您在问题中发布的代码基本上是使用锤子来驱动螺钉。更好的选择是说服他,您知道如何使用螺丝刀...

我可以想到几个示例...嗯,它们在MySQL上也可以,但是也应该可行。

在一个论坛上,全文索引需要花费一些时间进行更新。当用户提交帖子时,事务将更新主题表以增加帖子数和最后发布日期(从而锁定主题行),然后插入帖子,并且事务将保持锁定状态,直到全文索引完成更新

由于这是在带有太少RAM的锈钢上运行的,因此更新所述全文索引通常会导致盒子中单个慢速旋转驱动器上出现几秒钟的密集IO。

问题是单击该主题的人导致查询增加该主题的视图计数,这也要求锁定该主题行。因此,在全文索引更新时,没有人可以查看该主题。我的意思是,可以读取该行,但对其进行更新会锁定。 。冻结了整个论坛几秒钟,并导致大量请求堆积在Web服务器队列中。全文索引而无需任何锁定,然后使用帖子计数和最后发布日期以及COMMIT快速更新主题/论坛行。这就彻底解决了问题。它只是绕着几个查询,真的很简单。在锁定事务时要避免的其他事情示例:等待用户输入,从缓慢旋转的驱动器,网络IO等访问大量未缓存的数据。

当然,有时候,您别无选择,并且必须在拿着繁琐的锁的同时进行冗长的处理。有很多技巧(对数据的副本进行操作等),但是性能瓶颈通常来自于不是有意获取的锁,只需对查询重新排序即可解决问题。更好的是,知道编写查询时使用的锁...

我不会重复其他答案,但实际上...使用事务。您的问题是说服您的“ DBA”,而不是解决数据库的最重要功能...

#7 楼

TLDR:使用适当的隔离级别。

您正确地注意到了没有事务和“手动”恢复的方法可能非常复杂。高复杂度通常意味着要花费更多的时间来实现它,而要花费更多的时间来解决错误(因为复杂性会导致实现中出现更多错误)。这意味着这种方法会使您的客户付出更多的代价。

“ dba”同事的主要关注点是性能。改善它的一种方法是使用适当的隔离级别。假设您有一个向用户提供某种概述数据的过程。这样的过程不一定必须使用SERIALIZABLE隔离级别。在许多情况下,READ UNCOMMITTED足够了。这意味着,此过程不会被您创建或修改某些数据的事务阻止。 ,向您的客户说明性能优势。然后相应地调整这些功能/过程。

#8 楼

您还可以决定使用内存中OLTP表。它们当然仍然使用事务,但是没有阻塞。
不是阻塞所有操作都会成功,但是在提交阶段,引擎将检查事务冲突,并且其中之一提交可能失败。 Microsoft使用术语“乐观锁定”。
如果伸缩问题是由两个写操作之间的冲突引起的,例如两个并发事务试图更新同一行,则内存中OLTP会让一个事务成功而另一个事务失败交易。必须显式或隐式重新提交失败的事务,然后重试该事务。
更多内容:内存OLTP

#9 楼

有一种方法可以在有限的范围内使用事务,即将数据模型更改为面向对象。因此,与其在多个表中存储例如某个人的人口统计数据,将它们彼此关联并进行交易,不如将其存储在单个JSON文档中,而是将其存储在一个字段中。当然,解决领域扩展问题是另一个设计挑战,最好由开发人员而不是DBA完成