要求不要使用事务并使用替代方法来模拟事务


43

我从事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的理解,以便在这种情况下事务将回滚。

感谢您的提前反馈!


4
不符合其指定目的的评论已删除,或移至“社区Wiki”答案。
保罗·怀特

Answers:


61

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

将您自己称为“ DBA”的人建议的交易模拟无法确保交易处理的四个必不可少的属性中的三个,因为它只能解决“软”错误,而不能处理“硬”错误,例如网络断开,电源中断,磁盘故障等。

  • 原子性:失败。如果在伪交易的中间某处发生“硬”错误,则更改将是非原子的。

  • 一致性:失败。从上面可以看出,由于“硬”错误,您的数据将处于不一致状态。

  • 隔离:失败。并发伪事务可能会在完成之前更改由伪事务修改的某些数据。

  • 耐久性:成功。您所做的更改是持久的,数据库服务器将确保该更改;这是您同事无法解决的唯一问题。

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

总之,如果可以的话,请忽略“ DBA”的建议,如果有精神的话请与之抗争,如果出现特定的并发问题,请返回此处。


14

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

严重性为20或更高的错误将停止会话的SQL Server数据库引擎任务处理。如果发生严重性为20或更高的错误,并且数据库连接没有中断,则TRY ... CATCH将处理该错误。

注意,例如客户端中断请求或断开的客户端连接。

当会话由系统管理员使用KILL语句结束时。

...

编译错误(例如语法错误)会阻止批处理运行。

由于延迟的名称解析而发生的错误。

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


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

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

10

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

这对于交易是可行的解决方法吗?

dan-guzman:不,CATCH由于客户端API取消了该批处理,因此在查询超时的情况下永远不会执行该块。没有事务,SET XACT_ABORT ON就不能回滚除当前语句以外的任何内容。

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

rbarryyoung:如果他们遇到了很多阻塞,那么他们要么需要修复数据设计,合理化表访问顺序,要么使用更合适的隔离级别。他们假设他们的问题(以及对它的理解不了解)将成为您的问题。数百万其他数据库的证据表明,事实并非如此。

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

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


5

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

如果使用显式事务(回滚/提交)存在阻塞问题,请将DBA指向Internet,以获取一些解决该问题的好主意。

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

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

但这并非总是如此。深入研究UPDATE语句的执行,我们发现它也涉及首先执行SELECT语句。这是一种特殊且经常出现的方案,其中查询更新相互排斥的行集。与流行的看法相反,此处的INDEXES可以导致数据库引擎性能的显着提高。


4

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

伪造的交易删除不保证运行或成功。如果在假交易期间关闭数据库服务器,则某些但不是全部影响都会保留。也不能保证它们以事务回滚的肯定方式成功。

此策略可能适用于插入操作,但绝对不适用于更新或删除操作(无时间机器SQL语句)。

如果严格的事务并发导致阻塞,则有很多解决方案,甚至是降低保护级别的解决方案……这些都是解决问题的正确方法。

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


4

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

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

我认为这个人将“特定的情况下,错误的代码导致糟糕的性能”与“所有事务都是错误的”混为一谈。我不希望有能力的DBA犯这个错误,所以这真的很奇怪。也许他在使用一些糟糕的代码方面经历了非常糟糕的经历?

考虑这样的情况:

BEGIN
UPDATE or DELETE some row, which takes locks it
...do something that takes a while
...perform other queries
COMMIT

这种事务使用方式拥有一个锁(或几个锁),这意味着到达同一行的其他事务将不得不等待。如果锁保持了很长时间,特别是如果许多其他事务想要锁定同一行,则这确实会损害性能。

您可以问他为什么不使用事务这个奇怪的错误主意,什么类型的查询有问题,等等。然后尝试说服他,您一定会避免类似的不良情况,监视锁的使用情况并表演,让他放心等等

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

我可以想到几个示例……好吧,它们在MySQL上,但是也应该起作用。

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

由于这是在具有很少RAM的rustbucket上运行的,因此更新所述全文索引通常会导致盒子中单个慢速旋转驱动器上出现几秒钟的强烈随机IO。

问题是,单击主题的人导致查询增加了对该主题的视图计数,这也要求锁定主题行。因此,在全文索引更新时,没有人可以查看该主题。我的意思是,可以读取该行,但是对其进行更新将锁定。

更糟糕的是,发贴会更新父论坛表上的发帖数,并在全文索引更新时保持锁定...这冻结了整个论坛几秒钟,并导致大量请求堆积在Web服务器队列中。

解决方案是按正确的顺序获取锁:BEGIN,插入帖子并更新全文索引,而无需获取任何锁,然后使用帖子计数和最后发布日期以及COMMIT快速更新主题/论坛行。这就彻底解决了问题。它只是绕着一些查询,非常简单。

在这种情况下,事务不是问题……它是在进行冗长的操作之前获得了不必要的锁定。在锁定事务时要避免的其他事情示例:等待用户输入,从慢速旋转的驱动器,网络IO等访问大量未缓存的数据。

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

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


3

TLDR:使用适当的隔离级别

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

您的“ dba”同事最关心的是性能。改善它的一种方法是使用适当的隔离级别。假设您有一个向用户提供某种概述数据的过程。这样的过程不一定必须使用SERIALIZABLE隔离级别。在许多情况下,READ UNCOMMITTED足够了。这意味着,此过程不会被您创建或修改某些数据的事务阻止

我建议您检查数据库中所有现有的功能/过程,评估每个功能/过程的合理隔离级别,并向客户说明性能优势。然后相应地调整这些功能/过程。


2

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


-5

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.