【问题标题】:Do multiple DML statements in one stored procedure cause a performance hit?一个存储过程中的多个 DML 语句是否会导致性能下降?
【发布时间】:2015-01-09 11:11:54
【问题描述】:

目前我正在使用 MSSQL 服务器(2008R2 或 2012),我想问一下将多个 DML 语句放在一个过程中是否会导致显着的性能下降(下降)?

据我所知,这里的经验法则是将每个 DML 语句放入单独的存储过程中,并从一个包装过程中调用它们。

据我所知,使性能变差的事实是,当多个 DML 位于一个过程中时,无法为该过程创建和存储好的执行计划。另一方面,当 DML 在自己的过程中分离时,会为每个过程制定适当的执行计划。

最后值得一提的是,我将把所有 DML 包装在一个显式事务中 - 无论是在一个过程中还是在单独的过程中。

一个过程中的多个 DML 语句真的有那么大的问题吗?

谢谢。

【问题讨论】:

    标签: sql-server performance stored-procedures dml


    【解决方案1】:

    你问得很好,因为你被告知的不是真的。多个 DML 语句,无论是在单个存储过程中还是分散在多个 DML 语句中,只有在通过显式 BEGIN TRAN 和 COMMIT / ROLLBACK 显式分组时才会作为一个组相互影响。你应该把逻辑放在最有意义的地方(意思是最容易理解、可读、可维护的地方)。

    此外,如果一个 proc 无论如何都要调用多个 proc,那么将它们放在一个 proc 和多个 proc 中在概念上的真正区别是什么?当一个 proc 调用多个 proc 且每个 proc 都有 DML 语句时,这个人认为将多个 DML 语句放入单个 proc 时,存储过程的任何理论属性是否也会使事情变得更糟?最后,您仍然有一个入口点负责多个 DML 调用。

    例如:

    INSERT ....
    
    DELETE ....
    

    是两个单独的事务,因为每个查询本身都是一个事务(除非在显式事务中分组)。那么:

    BEGIN TRAN
    
       INSERT ....
    
       DELETE ....
    
    COMMIT TRAN
    

    是单笔交易。在一个事务中拥有多个 DML 语句可能由于并发(即阻塞/死锁)而存在性能问题,但如果操作要求两个 DML 语句在概念上都是一个原子工作单元,那么它们需要在显式事务中组合在一起。

    现在,鉴于上面的第一个示例有两个 DML 语句并且没有显式事务,假设它们在一个存储过程中,这与以下没有什么不同:

    CREATE PROC MasterProc
    AS
       EXEC SubProc1;
    
       EXEC SubProc2;
    GO
    
    CREATE PROC SubProc1
    AS
       INSERT ...
    GO
    
    CREATE PROC SubProc2
    AS
       DELETE ...
    GO
    

    并且,给定第二个示例,其中两个 DML 语句都分组在显式事务中,假设它们在存储过程中,这与(使用上述相同的两个 SubProc* 过程)没有什么不同:

    CREATE PROC MasterProc
    AS
       BEGIN TRAN;
    
          EXEC SubProc1;
    
          EXEC SubProc2;
    
       COMMIT TRAN;
    GO
    

    编辑:
    一些额外的信息是,显然单个存储过程中的多个 DML 语句对性能更差的原因是(据说)SQL Server 无法获得组合的“正确”(即好的?)执行计划,但是如果 DML 语句是分开的,那么 SQL Server 可以为每个单独的存储过程生成“适当的”(即好的?)执行计划。

    嗯,执行计划实际上是在查询级别。当然,在 SQL Server 2000 之前,只能重新编译整个过程,但从 SQL Server 2005 开始,可以进行语句级重新编译。但无论哪种方式,每个查询都是自己优化的,因此将它们分开似乎并没有带来任何好处。事实上,鉴于局部变量(包括表变量)不能在存储过程之间共享,如果您必须将每个变量作为输入参数传递并且将其中的一个或多个传回。该代码将难以维护和调试。

    编辑 2:
    以下是一些有关执行计划缓存和重新编译信息的资源:

    • Execution Plan Caching and Reuse
      (这是从 SQL Server 2008 R2 开始的;我正在尝试查找更新的内容)
    • DBCC FREEPROCCACHE
      (当前文档;请注意,从 SQL Server 2008 开始,可以通过 [ ({ plan_handle | sql_handle | pool_name }) ] 删除单个计划)
    • Caching Mechanisms
      (这似乎是从 SQL Server 2005 开始编写的,因为最后一句是“请注意,没有办法强制 SQL Server 从缓存中仅删除一个查询计划。”如上所述,这不再是正确的,因为SQL Server 2008 的版本)。
    • Recompile a Stored Procedure

    还请注意,这些文档确实提到了在对象(即存储过程等)级别缓存的执行计划。但是,这并不意味着 DML 语句作为一个整体进行了优化,并且如果分离出来会有所不同。将 DML 语句分离为单独的存储过程的唯一真正好处是,您可以(从 SQL Server 2008 开始)通过DBCC FREEPROCCACHE ({ plan_handle | sql_handle }) 手动对它们单独发出重新编译请求。但是,如果这只是偶尔需要的东西,那么重新编译整个存储过程并没有什么坏处,与笨重的代码分离相比,这可能成本更低。而且,如果它是经常需要的东西,那么 DML 语句本身可以添加一个OPTION (RECOMPILE),这不会影响该存储过程中的其他查询。

    编辑 3:(希望是最终版 :)
    我们不要忘记,这仍然是我们正在做的计算机科学,几乎所有东西都是可测试的。用一个表和几个 proc 设置一个场景(如上面显示的示例)并运行调用子存储过程的“主”proc 以及多合一 proc 应该是相当容易的。您可以在运行时查看 SSMS 中的“实际执行计划”,也可以从计划缓存中提取计划(如上面的链接资源所示)。所以从技术上讲,没有人需要依赖这个建议使用单独存储过程的神秘人,甚至没有人相信这个想法充其量是过时的,最坏的情况是纯粹的神话; SQL Server 会准确地告诉你它在任何给定情况下会做什么:)。

    【讨论】:

    • 感谢您的回复。我被告知让事情变得更糟的是,当 DML 在一个程序中时,无法为该程序完成正确的执行计划。但是单独拥有 DML 将为每个子过程制定适当的执行计划。我之前应该提到的一件事 - 我将使用 begin tran 和 commit tran 包装所有调用。所以他们会耦合
    • @Emil :你应该把关于你被告知的额外信息放在主要问题中,以便每个人都更清楚整体问题。但这并没有什么不同,因为执行计划是基于每个查询,而不是每个事务。
    • @srutzky - 你可能是对的;)我只知道这种变化是很久以前的,当时非常重要。这就是为什么我很惊讶给埃米尔建议的人并不知道更好。它只是表明在我们的领域中总是有新的东西需要学习。
    • @DB101 :是的,我刚刚发现文档说从 2005 年开始,您可以进行语句级重新编译。此外,仅仅因为在此之前您只能重新编译对象,这仍然不能使给出此建议的人正确了解每个查询的优化方式。对象级别与语句级别的重新编译是一个管理问题,因此,是的,在 2005 年之前更难在粒度级别上进行管理,但我认为这并不意味着多个 DML 语句如果分开会得到不同的计划。无论哪种方式,我都添加了一些资源的链接:)。
    • @srutzky:感谢您非常详尽的解释,它很好地回答了我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 2012-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-13
    • 1970-01-01
    相关资源
    最近更新 更多