我知道存储过程通过执行路径(比应用程序中的内联sql)更有效。但是,当按下时,我对原因并不了解。
我想知道这一点的技术原因(以便以后可以向某人解释)。
谁能帮我制定一个好的答案?
我知道存储过程通过执行路径(比应用程序中的内联sql)更有效。但是,当按下时,我对原因并不了解。
我想知道这一点的技术原因(以便以后可以向某人解释)。
谁能帮我制定一个好的答案?
Answers:
我相信这一观点在某一时刻是正确的,但在当前版本的SQL Server中却不是。整个问题是,在过去,临时SQL语句无法正确优化,因为SQL Server只能在批处理级别进行优化/编译。现在我们有了语句级的优化,因此来自应用程序的参数正确的查询可以利用与存储过程中嵌入的查询相同的执行计划。
由于以下原因,我仍然更喜欢DBA方面的存储过程(其中一些会对性能产生巨大影响):
sys.sql_modules
,用于引用特定对象)使每个人的生活变得更加轻松。SET ANSI_WARNINGS ON
,另一个可以有SET ANSI_WARNINGS OFF
,并且每个应用程序都有自己的计划副本。他们获得的计划取决于所使用的参数,适当的统计信息等。在每种情况下,首次调用查询都可能导致不同的计划,从而导致非常不同的性能。话虽如此,这个问题很可能引发比技术辩论更多的宗教争论。如果看到这种情况,我们可能会关闭它。
我尊重提交者,但我谦虚地反对所提供的答案,并非出于“宗教原因”。换句话说,我认为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
最好的,
亨利
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调用和需要更详细的报告使用完全相同的业务逻辑方法,以确保我不会重复复杂的逻辑。
有充分的理由使用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。