存储过程与内联SQL


27

我知道存储过程通过执行路径(比应用程序中的内联sql)更有效。但是,当按下时,我对原因并不了解。

我想知道这一点的技术原因(以便以后可以向某人解释)。

谁能帮我制定一个好的答案?


1
一个正确的参数化查询一样好作为一个存储过程,从性能的角度来看。两者都在首次使用之前进行编译,都将在以后的执行中重用缓存的执行计划,两个计划都存储在相同的计划缓存中,并且都将以相同的名称处理。今天,在SQL Server中,存储过程不再具有性能优势。
marc_s 2013年

@marc_s如果查询相同,则为true。但是,正如我在回答中指出的那样,即席查询的某些特征甚至对于看起来相同的查询也可能是性能问题。
阿龙贝特朗

Answers:


42

我相信这一观点在某一时刻是正确的,但在当前版本的SQL Server中却不是。整个问题是,在过去,临时SQL语句无法正确优化,因为SQL Server只能在批处理级别进行优化/编译。现在我们有了语句级的优化,因此来自应用程序的参数正确的查询可以利用与存储过程中嵌入的查询相同的执行计划。

由于以下原因,我仍然更喜欢DBA方面的存储过程(其中一些会对性能产生巨大影响):

  • 如果我有多个重复使用相同查询的应用程序,则存储过程将封装该逻辑,而不是在不同的代码库中多次散布相同的即席查询。重用相同查询的应用程序也可能受到计划缓存膨胀的影响,除非逐字复制它们。大小写和空格之间的差异甚至都可能导致同一计划的多个版本被存储(浪费)。
  • 我可以检查查询并对其进行故障诊断,而无需访问应用程序源代码或运行昂贵的跟踪以准确查看应用程序在做什么。
  • 我还可以控制(并事先知道)应用程序可以运行哪些查询,可以访问哪些表以及在什么上下文中等等。如果开发人员在其应用程序中临时编写查询,他们要么必须每当他们需要访问我不知道或无法预测的桌子时,或者如果我不太负责任/热情和/或对安全性意识不强时,就来拉我的衬衫袖子,我只是要推广一下用户使用dbo,以便他们不再烦扰我。通常,当开发人员人数超过DBA或DBA固执时,才执行此操作。最后一点是我们的坏处,我们需要在提供所需查询方面做得更好。
  • 与此相关的是,一组存储过程是一种非常简单的方法,可以准确地盘点系统上可能正在运行的查询。一旦允许应用程序绕过过程并提交自己的即席查询以查找它们,我就必须运行一个覆盖整个业务周期的跟踪,或者解析所有应用程序代码(同样,我可能无权访问)以查找任何看起来像查询的内容。能够查看存储过程的列表(并grep表示单个源sys.sql_modules,用于引用特定对象)使每个人的生活变得更加轻松。
  • 我可以花更多的时间来防止SQL注入。即使我接受输入并使用动态SQL执行它,我也可以控制很多允许发生的事情。在构造内联SQL语句时,我无法控制开发人员的工作。
  • 我可以优化查询,而无需访问应用程序源代码,进行更改的能力,有效进行应用程序语言的知识,重新编译和重新部署的权限(不必担心)应用等。如果分发了该应用,这尤其成问题。
  • 我可以在存储过程中强制使用某些设置选项,以避免单个查询受应用程序中某些慢速程序的影响,在SSMS中是快速的吗?问题。这意味着对于两个调用临时查询的不同应用程序,一个可以有SET ANSI_WARNINGS ON,另一个可以有SET ANSI_WARNINGS OFF,并且每个应用程序都有自己的计划副本。他们获得的计划取决于所使用的参数,适当的统计信息等。在每种情况下,首次调用查询都可能导致不同的计划,从而导致非常不同的性能。
  • 与某些ORM不同,我可以控制诸如数据类型和参数使用方式之类的东西-诸如EF之类的某些较早版本会根据参数的长度对查询进行参数化,因此如果我有一个参数N'Smith'和另一个N'约翰逊,我会得到两个不同版本的计划。他们已解决此问题。他们已解决此问题,但还有什么坏处?
  • 我可以做ORM和其他“有用”框架和库尚不支持的事情。

话虽如此,这个问题很可能引发比技术辩论更多的宗教争论。如果看到这种情况,我们可能会关闭它。


2
存储过程的另一个原因?对于长而复杂的查询,您必须每次都将查询推送到服务器,除非它是一个sproc,否则基本上您只是在推送“ exec sprocname”和一些参数。这在慢速(或繁忙)的网络上可能会有所作为。
David Crowell 2014年

0

我尊重提交者,但我谦虚地反对所提供的答案,并非出于“宗教原因”。换句话说,我认为Microsoft没有提供任何可减少使用存储过程指南的需求的工具。

提供给开发人员的任何建议使用原始文本SQL查询的指南都必须有许多警告,以便我认为最明智的建议是大力鼓励使用存储过程,并劝阻您的开发团队参与实践将SQL语句嵌入代码中,或在SQL SPROC(存储过程)之外提交原始的,基于纯文本的原始SQL请求。

我认为,为什么要使用SPROC的问题的简单答案就是提交者的推测:对SPROC进行了解析,优化和编译。这样,由于您保存了查询的静态表示,因此通常会缓存它们的查询/执行计划,并且通常只通过参数来更改它,而对于复制/粘贴的SQL语句(可能会变体)则不是这样从页面到页面以及组件/层,并且经常变化到可以在呼叫之间指定不同的表,甚至是数据库名称的程度。允许这种类型的动态临时根据一些非常严格的规则,SQL提交极大地降低了DB Engine为您的即席语句重新使用查询计划的可能性。在这里,我将动态即席查询(按照提出的问题的精神)与使用高效的System SPROC sp_executesql进行区分。

更具体地说,有以下组件:

  • 串行和并行查询计划不包含用户上下文,并且允许数据库引擎重用。
  • 执行上下文,允许具有不同数据参数的新用户重用查询计划。
  • 过程引擎缓存是数据库引擎查询的内容,目的是提高我们追求的效率。

当从网页发出SQL语句(称为“临时语句”)时,引擎将寻找现有的执行计划来处理该请求。因为这是从用户提交的文本,所以如果有效,将对其进行提取,解析,编译和执行。此时,它将收到零的查询费用。当数据库引擎使用其算法来确定从缓存中逐出哪些执行计划时,将使用查询成本。

默认情况下,临时查询的原始查询成本值为零。在随后由另一个用户进程(或相同的用户进程)执行完全相同的即席查询文本时,当前查询成本将重置为原始编译成本。由于我们的临时查询编译成本为零,因此对于重用的可能性而言,这并不是一个好兆头。显然,零是最小的整数,但是为什么要逐出它呢?

当出现内存压力时,如果您有一个经常使用的站点,这些压力将很大,数据库引擎将使用清除算法来确定如何回收过程高速缓存正在使用的内存。它使用当前查询成本来决定驱逐哪些计划。您可能会猜到,成本为零的计划是第一个从缓存中撤出的计划,因为零本质上意味着“该计划没有当前用户或对该计划的引用”。

  • 注意:临时执行计划-当前成本由每个用户进程和计划的原始编译成本增加。但是,任何计划的最大成本都不能超过其原始编译成本……在临时查询的情况下……为零。因此,它将被该值“增加” ...零-这实际上意味着它将保持最低的成本计划。

因此,很可能在出现内存压力时首先取消这种计划。

因此,如果您的服务器具有大量超出“您的需求”的内存,则可能不会像只有“足够”内存来处理其工作负载的繁忙服务器那样经常遇到此问题。(抱歉,服务器内存容量和利用率在某种程度上是主观/相对的,尽管算法不是。)

现在,如果我对一个或多个要点确实不正确,那么我肯定会被纠正。

最后,作者写道:

“现在我们有了语句级的优化,因此来自应用程序的适当参数化的查询可以利用与存储过程中嵌入的查询相同的执行计划。”

我相信作者指的是“针对临时工作负载进行优化”选项。

如果是这样,则此选项允许执行两步过程,从而避免立即将完整的查询计划发送到过程高速缓存。它在那里只发送一个较小的查询存根。如果在查询存根仍在过程高速缓存中的同时将确切的查询调用发送回服务器,则完整的查询执行计划将被保存到过程高速缓存中。这样可以节省内存,其内存在压力事件,可以允许驱逐算法不经常驱逐存根比被缓存更大的查询计划。同样,这取决于服务器的内存和利用率。

但是,您必须打开此选项,因为默认情况下它是关闭的。

最后,我想强调的是,开发人员之所以经常将SQL嵌入到页面,组件和其他地方,是因为他们希望灵活并向数据库引擎提交动态SQL查询。因此,在实际的用例中,当向SQL Server提交临时查询时,不太可能发生与我们寻求的缓存/效率相同的提交文本,即“ call-over-call”。

有关更多信息,请参见:

https://technet.microsoft.com/zh-cn/library/ms181055(v=sql.105).aspx
http://sqlmag.com/database-performance-tuning/don-t-fear-dynamic-sql

最好的,
亨利


4
我已经仔细阅读了您的帖子的几段内容,两次或三遍,但我仍然不知道您想表达什么想法。在某些情况下,您似乎在句子结尾说的是与句子刚开始尝试说的完全相反的句子。您确实需要仔细校对和编辑此提交。
Pieter Geerkens 2015年

感谢您对Pieter的反馈。如果是这样,我可能会缩短句子以使观点更清楚。您能否提供一个例子,说明我似乎在说出与原始想法相反的地方?非常感激。
亨利(Henry)

不,我不是说针对临时工作负载进行优化,而是指语句级的优化。例如,在SQL Server 2000中,存储过程将作为一个整体进行编译,因此应用程序无法为自己的临时查询重用计划,而该查询恰好与该过程中的某些内容匹配。我会说我同意Pieter-您所说的很多事情很难理解。诸如“我相信微软没有提供任何设施可以减少使用存储过程的指南的需求。” 是不必要的复杂,需要太多的解析来理解。恕我直言。
亚伦·伯特兰

1
似乎您对“即席” sql的厌恶是基于这样的想法,即sql在执行之间有所不同……当涉及参数化时,这是完全不正确的。
b_levitt

0

TLDR:只要对内联sql进行参数化,两者之间就不会有明显的性能差异。

这就是我逐渐淘汰存储过程的原因:

  • 我们运行一个“ beta”应用程序环境-与生产环境平行的环境,共享生产数据库。因为db代码是在应用程序级别上,并且很少发生db结构更改,所以我们可以允许人们确认QA以外的新功能,并在生产部署窗口之外进行部署,但仍提供生产功能和非关键性的修补程序。如果一半的应用程序代码在数据库中,则这是不可能的。

  • 我们在数据库级别(章鱼+ dacpacs)练习devop。但是,虽然基本上可以清除并替换和替换业务层和上层,而恢复却可以相反,但是对于必须进行数据库的增量和潜在破坏性更改而言,情况并非如此。因此,我们希望使数据库部署更轻松,更不频繁。

  • 为了避免相同代码的几乎完全相同的可选参数副本,我们通常会使用“其中@var为null或@ var = table.field”模式。使用存储的proc,尽管意图非常不同,但您可能会获得相同的执行计划,因此会遇到性能问题或使用“重新编译”提示消除缓存的计划。但是,通过在sql末尾添加一个“签名”注释的简单代码,我们可以基于哪些变量为null强制执行不同的计划(不要将其解释为所有变量组合的不同计划-仅null与不为null)。

  • 我可以对结果进行重大更改,而只需对SQL进行少量更改即可。例如,我可以使用一条包含两个CTE的语句“ Raw”和“ ReportReady”。没有什么说必须使用两个CTE我的sql语句可以是:

    ...

    从{(format)}中选择*”

这使我能够为简化的api调用和需要更详细的报告使用完全相同的业务逻辑方法,以确保我不会重复复杂的逻辑。

  • 当您具有“仅procs”规则时,您在绝大多数sql中最终会产生大量冗余,最终导致CRUD-绑定所有参数,并在proc签名中列出所有这些参数,(现在您位于不同项目中的其他文件中),则可以将这些简单参数映射到其列。这创造了相当脱节的开发经验。

有充分的理由使用proc:

  • 安全性-应用程序必须经过另一层。如果不允许应用程序服务帐户触摸表,而仅对proc具有“执行”权限,则您将获得一些额外的保护。这不是给定的,因为它确实有成本,但这是可能的。

  • 重用-尽管我会说重用应该主要发生在业务层,以确保您不绕过与数据库无关的业务规则,但我们仍然具有时空的,低级的“随处使用”类型的实用程序和功能。

有一些参数实际上并不支持proc或可以轻松缓解IMO:

  • 重用-上面我将其称为“加号”,但在这里也想提及重用应该在业务层进行。当业务层也可能正在检查其他非数据库服务时,不应将插入记录的proc视为“可重用”。

  • 缓存计划膨胀-这将是一个唯一的问题,如果您是串联值而不是参数化。每个查询很少获得一个以上计划的事实实际上常常在查询中带有“或”时伤害您

  • 语句大小-相对于返回的数据,在proc名称上额外增加kb的sql语句通常可以忽略不计。如果对实体没问题,对我也没关系。

  • 查看确切的查询-使查询易于在代码中查找就像将调用位置作为注释添加到代码中一样简单。使代码可从c#代码复制到ssms就像一些创造性的插值和注释用法一样简单:

        //Usage /*{SSMSOnly_}*/Pure Sql To run in SSMS/*{_SSMSOnly}*/
        const string SSMSOnly_ = "*//*<SSMSOnly>/*";
        const string _SSMSOnly = "*/</SSMSOnly>";
        //Usage /*{NetOnly_}{InterpolationVariable}{_NetOnly}*/
        const string NetOnly_ = "*/";
        const string _NetOnly = "/*";
    
  • SQL注入-参数化您的查询。做完了 如果proc改为使用动态sql,则实际上可以撤消此操作。

  • 绕过部署-我们也在数据库级别上实践devop,因此这不是我们的选择。

  • “应用程序速度慢,SSMS速度快”-这是一个计划缓存问题,会影响到双方。set选项只会导致编译一个新计划,该计划似乎可以解决THE One SET OFF变量的问题。这只能回答为什么您看到不同的结果的原因-设置选项本身不能解决参数嗅探的问题。

  • 内联sql执行计划未缓存-完全为false。像proc名称一样,参数化的语句会快速哈希,然后通过该哈希搜索计划。100%一样。

  • 明确地说,我说的是原始内联sql,不是从ORM生成的代码-我们仅使用Dapper,它最多是一个微型ORM。

https://weblogs.asp.net/fbouma/38178

/programming//a/15277/852208

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.