【问题标题】:Having TRANSACTION In All Queries在所有查询中都有 TRANSACTION
【发布时间】:2010-07-08 08:57:58
【问题描述】:

您认为在存储过程中始终围绕 SQL 语句进行事务是一种好习惯吗?我正要优化我公司的这个遗留应用程序,我发现的一件事是每个存储过程都有BEGIN TRANSACTION。即使是具有单个 select 或 update 语句的过程也有一个。我认为如果执行多个操作,但不只是一个操作,拥有BEGIN TRANSACTION 会很好。我可能是错的,这就是为什么我需要别人给我建议。谢谢你们的时间,伙计们。

【问题讨论】:

    标签: sql sql-server-2005 transactions


    【解决方案1】:

    这是完全没有必要的,因为每个 SQL 语句都是原子执行的,即。就好像它已经在自己的事务中运行一样。事实上,打开不必要的事务会导致锁定增加,甚至死锁。只要与数据库的连接处于打开状态,忘记将 COMMIT 与 BEGIN 匹配就会使事务保持打开状态,并干扰同一连接中的其他事务。

    这样的编码几乎可以肯定意味着编写代码的人在数据库编程方面不是很有经验,而且很可能还有其他问题。

    【讨论】:

      【解决方案2】:

      我能看到的唯一可能原因是,如果您有可能因为 SQL 失败以外的原因需要回滚事务。

      但是,如果代码是字面意思

      begin transaction
      statement
      commit
      

      然后我认为绝对没有理由使用显式事务,并且可能因为it's always been done that way 而完成。

      【讨论】:

        【解决方案3】:

        我不知道不只是对这些语句使用自动提交事务有什么好处。

        在任何地方都使用显式事务的可能缺点可能是它只会给代码增加混乱,因此很难看出何时使用显式事务来确保多个语句的正确性。

        此外,除非小心(例如使用SET XACT_ABORT ON),否则它会增加事务保持打开状态并持有锁的风险。

        还有一个 对性能的影响,如@8kb's answer 所示。这说明了它使用 Visual Studio Profiler 的另一种方式。

        设置

        (针对空表进行测试)

        CREATE TABLE T (X INT)
        

        显式

        SET NOCOUNT ON
        
        DECLARE @X INT
        
        WHILE ( 1 = 1 )
          BEGIN
              BEGIN TRAN
        
              SELECT @X = X
              FROM   T
        
              COMMIT
          END
        

        自动提交

        SET NOCOUNT ON
        
        DECLARE @X INT
        
        WHILE ( 1 = 1 )
          BEGIN
              SELECT @X = X
              FROM   T
          END 
        

        它们最终都在CMsqlXactImp::BeginCMsqlXactImp::Commit 上花费了时间,但对于显式事务的情况,它在这些方法中花费了相当多的执行时间,因此做有用工作的时间更少。

        +--------------------------------+----------+----------+
        |                                | Auto     | Explicit |
        +--------------------------------+----------+----------+
        | CXStmtQuery::ErsqExecuteQuery  | 35.16%   | 25.06%   |
        | CXStmtQuery::XretSchemaChanged | 20.71%   | 14.89%   |
        | CMsqlXactImp::Begin            | 5.06%    | 13%      |
        | CMsqlXactImp::Commit           | 12.41%   | 24.03%   |
        +--------------------------------+----------+----------+
        

        【讨论】:

          【解决方案4】:

          在执行多次插入/更新/删除时,最好有一个事务来保证操作的原子性,并保证操作的所有任务都执行或不执行。

          对于单个插入/更新/删除语句,这取决于您正在执行什么样的操作(从业务层的角度来看)以及它的重要性。如果你在单次插入/更新/删除之前进行了一些计算,那么更好地使用事务,可能是在你检索到插入/更新/删除的数据后,一些数据发生了变化。

          【讨论】:

            【解决方案5】:

            一个优点是您可以添加另一个 INSERT(例如),它已经很安全了。

            再一次,如果一个存储过程调用另一个存储过程,您也会遇到嵌套事务的问题。内部回滚会导致错误 266。

            如果每个调用都是没有嵌套的简单 CRUD,那么它是没有意义的:但如果你嵌套或在 TXN 之前有多次写入,那么最好有一个一致的模板。

            【讨论】:

              【解决方案6】:

              您提到您将优化这个旧版应用。

              为了提高性能,您可以做的首要且最简单的事情之一是删除只执行 SELECT 的存储过程的所有 BEGIN TRAN 和 COMMIT TRAN。

              这里有一个简单的测试来演示:

              /* Compare basic SELECT times with and without a transaction */
              
              DECLARE @date DATETIME2
              DECLARE @noTran INT
              DECLARE @withTran INT
              
              SET @noTran = 0
              SET @withTran = 0
              
              DECLARE @t TABLE (ColA INT)
              INSERT @t VALUES (1)
              
              DECLARE 
                @count INT,
                @value INT
              
              SET @count = 1
              
              WHILE @count < 1000000
              BEGIN
              
                SET @date = GETDATE()
                SELECT @value = ColA FROM @t WHERE ColA = 1
                SET @noTran = @noTran + DATEDIFF(MICROSECOND, @date, GETDATE())
              
                SET @date = GETDATE()
                BEGIN TRAN
                SELECT @value = ColA FROM @t WHERE ColA = 1
                COMMIT TRAN
                SET @withTran = @withTran + DATEDIFF(MICROSECOND, @date, GETDATE())
              
                SET @count = @count + 1
              END
              
              SELECT 
                @noTran / 1000000. AS Seconds_NoTransaction, 
                @withTran / 1000000. AS Seconds_WithTransaction
              
              /** Results **/
              
              Seconds_NoTransaction                   Seconds_WithTransaction
              --------------------------------------- ---------------------------------------
              14.23600000                             18.08300000
              

              您可以看到与事务相关的明确开销。

              注意:这是假设您的这些存储过程没有使用任何特殊的隔离级别或锁定提示(例如处理悲观并发)。在这种情况下,显然你会想要保留它们。

              所以为了回答这个问题,我只会保留您实际尝试保持数据修改完整性的事务,以防代码、SQL Server 或硬件出现错误。

              【讨论】:

              • 一个有趣的结果。即使每次调用的开销仅为 4 µs 左右。
              • 如果您添加 SET NOCOUNT ON,这将完全改变它,并且没有 tran 始终更快。当前的代码正在测量处理它需要多长时间...... @Martin:尝试使用 set nocount on
              • @gbn - 我认为你的结论是相同的,尽管关于NOCOUNT 的观点你们俩都说显式事务比较慢。我已经扩展了我的答案以另一种方式显示。
              【解决方案7】:

              我只能说,像这样给每个存储过程放置一个事务块可能是新手的工作。

              事务只能放在有多个插入/更新语句的块中,除此之外,存储过程中不需要放置事务块。

              【讨论】:

                【解决方案8】:

                默认情况下,不应在每个存储过程中使用 BEGIN TRANSACTION / COMMIT 语法,除非您试图涵盖以下场景:

                1. 您包含 WITH MARK 选项是因为您希望支持将数据库从备份恢复到特定时间点。

                2. 您打算将代码从 SQL Server 移植到另一个数据库平台,如 Oracle。 Oracle 默认不提交事务。

                【讨论】:

                  猜你喜欢
                  • 2018-05-15
                  • 1970-01-01
                  • 2023-03-18
                  • 2021-10-30
                  • 2016-11-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多