为啥执行存储过程比脚本中的 SQL 查询更快?
Posted
技术标签:
【中文标题】为啥执行存储过程比脚本中的 SQL 查询更快?【英文标题】:Why execute stored procedures is faster than SQL query from a script?为什么执行存储过程比脚本中的 SQL 查询更快? 【发布时间】:2011-12-19 09:42:10 【问题描述】:事实上,如果我从我的应用程序调用存储过程,我需要连接到我的数据库。
那么,为什么调用“存储过程”应该比“传递 SQL 查询”字符串更快?
【问题讨论】:
这些天来,存储过程确实不再有太多的速度优势了。正确参数化的查询将与存储过程一样有效。仅凭这一点并不是使用存储过程的好理由 【参考方案1】:SQL Server 基本上通过这些步骤来执行任何查询(存储过程调用或即席 SQL 语句):
1) 语法检查查询 2) 如果没问题 - 它检查计划缓存以查看它是否已经有该查询的执行计划 3) 如果有执行计划 - 该计划被(重新)使用并执行查询 4)如果还没有计划,则确定执行计划 5) 该计划被存储到计划缓存中以供以后重用 6) 查询被执行
重点是:即席 SQL 和存储过程在处理上没有什么不同。
如果 ad-hoc SQL 查询正确使用参数 - 无论如何它应该使用参数,以防止 SQL 注入攻击 - 它的性能特征没有什么不同,而且绝对不会比执行存储过程差。
存储过程还有其他好处(例如,无需授予用户直接表访问权限),但就性能而言,使用适当参数化的即席 SQL 查询与使用存储过程一样高效程序。
更新:在非参数化查询上使用存储过程更好,主要有两个原因:
由于每个非参数化查询都是对 SQL Server 的新的、不同的查询,因此对于每个查询,它都必须经过确定执行计划的所有步骤(因此浪费时间- 并且还浪费计划缓存空间,因为将执行计划存储到计划缓存中最终并没有真正的帮助,因为该特定查询可能不会再次执行)
非参数化查询存在 SQL 注入攻击的风险,应不惜一切代价避免
【讨论】:
P.S. SP 被存储/缓存...但是例如,如果我有一个“where”子句,我从客户端传递了一个自定义值,则应该使用该输入/条件编译它;那么对于每个参数是否有一个专用的 SP? @markzzz: 不,在这种情况下,我会在存储过程的定义中添加一个参数(从调用者传递),并在存储过程的“主体”中使用该参数.这样,你有 one 存储的过程体,one(缓存)执行计划,你可以用任何可能的值调用它。 @marc_s:非参数化查询如何面临 sql 注入的风险,因为非参数化查询从不接受任何参数。如果我错了,请告诉我 值得一提的是,在某些情况下,SQL Server 可以参数化(也称为“自动参数化”)非参数化查询,在这种情况下,查询的性能也不会有所不同。 步骤顺序的来源是什么?您确定首先进行语法检查吗?我不明白为什么在解析语句之前不会首先发生简单的哈希然后检查该哈希的计划缓存,我猜它会在编译步骤之前发生。【参考方案2】:因为每次将查询字符串传递给 SQL Server 时,都必须编译代码等,存储过程已经编译并准备好在服务器上运行。
虽然这通常影响很小,但您通过网络发送的数据也较少。
编辑: 作为旁注,存储过程还有其他好处。
1) 安全性 - 由于实际查询存储在服务器上,因此您不会通过网络传输它,这意味着任何拦截您的网络流量的人都无法深入了解您的表结构。设计良好的 SP 也可以防止注入攻击。
2) 代码分离,将数据库代码保存在数据库中,将应用程序代码保存在应用程序中,几乎没有交叉,我发现这使得错误修复变得更好。
3) 可维护性和代码重用,您可以多次重用一个过程,而无需复制粘贴查询,如果您希望更新查询,您只需在一处更新即可。
4) 减少网络流量。如上所述,这对大多数人来说可能不是问题,但对于大型应用程序,您可以通过切换到使用存储过程来显着减少通过网络传输的数据量。
【讨论】:
任何存储过程都必须先进行分析,确定执行计划等,然后才能缓存。任何参数化查询都经过相同的过程,因此确实具有与存储过程相同的性能优势 这很有趣,谢谢,我一直认为这个过程实际上是针对存储过程进行了优化的。 但是执行计划不是无限期地存储在存储过程中,并且每次都为即席查询生成吗?事情可能已经改变,但我相信这就是过去的样子。这可能意味着使用存储过程可以获得巨大的性能提升。虽然如果我没记错的话,这取决于你在存储过程中所做的事情,如果它很复杂,执行计划可能仍然会在每次调用时重新生成。有cmets吗?【参考方案3】:您关于存储过程比 SQL 查询快的说法只是部分正确。解释一下:这些答案中的大多数已经解释了使用存储过程会生成和缓存查询计划。因此,如果您再次调用存储过程,SQL 引擎首先搜索其查询计划列表,如果找到匹配项,则使用优化计划。
传递一个普通的查询并没有这个优势,因为 SQL 引擎不知道会发生什么,因此它无法为您的查询找到匹配项。它从头开始制定计划,然后呈现您的结果。
好消息:您可以使用参数化查询(SQL 中的一项新功能)为您的查询启用计划缓存。这使您能够为查询生成计划,并且在您的情况下非常有效,因为您从代码传递的大多数查询保持不变,除了 Where 子句中的变量。还有一个设置,您可以在其中强制对所有查询进行参数化。在 MSDN 中搜索此主题,应该可以帮助您决定什么是最好的。
不过,也就是说,存储过程仍然是从应用程序与数据库交互的好方法,因为它提供了额外的安全层。
希望这对您有所帮助!
【讨论】:
【参考方案4】:这篇文章解释得很好: https://codingsight.com/dynamic-sql-vs-stored-procedure/
根据我的经验,存储过程肯定更快,因为减少了网络流量(不必发送整个查询)以及过程和查询计划的缓存。
我在一个填充了用户登录数据的表上运行了类似于以下的代码。
"select top 1 * from Logons where ComputerName=@ComputerName order by LogonTime desc"
对 7000 个计算机名称运行查询耗时 2 小时。
当我将查询放入存储过程中时,大约需要一分钟才能在 7000 个计算机名称上运行。
我敢肯定,如果您只运行一次查询,则每次查询花费 1 秒与 10 毫秒对人类没有太大影响。但是,如果您需要运行一千次查询,则相差 1000 秒(大约 16 分钟)与 10 秒。
【讨论】:
【参考方案5】:另外一个被忽略的问题,对比一下这个的web服务器和数据库服务器的网络流量-
执行 someproc @var1='blah', @var2='blah', @var3='blah'
到这里-
选择字段1、字段2、字段3、字段4、字段5、字段6....字段30 在 table2.field12 = table1.field12 上加入 table1 在哪里等等等等 and table1.field3 = @var1 and table2.field44 = @var2 and (table1.field1 is null or table1.field1 = @var3.......
看到区别了吗?对于我们 99% 的人来说,这可能无关紧要,但对于一些编写高性能应用程序的人来说,这可能是重要的,尽管可能有一些缓存或其他方法来处理这个问题。
我认为很多声称即席查询和存储过程之间没有区别的人通常将表用作他们使用的任何 ORM 的对象存储,这很好。仍然有很多大数据驱动的企业应用程序,无论对错,都有超过 1000 行的存储过程。你最终可能会在他们身上工作。此外,对于那些可能不得不每隔一段时间在生产环境中进行更改并需要绕过正式流程的人来说,在数据库中执行此操作比在生产编译代码中更容易很多。更改过程...完成。牛仔,可怕,邪恶,发生了。是的,我知道这样做在你的许多人心目中是不可原谅的罪过,是剪断的迹象……但它确实发生了。也是需要考虑的事情。
我知道,如果您希望所有实体都能为您很好地生成,那么最新的工具通常会让使用存储过程变得很麻烦,但有时存储过程仍然占有一席之地。
【讨论】:
假设您有一个一次显示 20 条记录的网格,但您的查询返回 20,000 条匹配记录。使用实体框架,您可以在代码中进行分页,从而将发送到浏览器的数据从 20,000 条记录减少到 20 条记录。但仍有 20,000 条记录从数据库服务器发送到 Web 服务器。使用存储过程,您可以在过程本身中处理分页逻辑,因此 20 条记录从数据库服务器发送到 Web 服务器,然后发送到客户端。减少数据库服务器和 Web 服务器之间的流量。其他需要考虑的事情。 [冗余],已删除。 现在使用 Entity Framework 不太正确,它似乎只处理获取所需的 20 条记录,而数据库服务器不会像我们的示例中那样吸收 20,000 条记录。尽管网络服务器和数据库服务器之间的数据流量仍然是一个问题。存储过程减少了这一点。【参考方案6】:存储过程被编译和缓存。但是 SQL 语句将与现有的执行计划进行比较,如果存在匹配则使用,因此在某种程度上抵消了任何优势。
多次执行后的实际性能差异是多少?
【讨论】:
?这是什么意思?结果是缓存还是只是 SQL 查询? @marc_s 我还在写答案。我想我应该知道规则。丛林法则等等。并且 sql 查询只有在第一次运行后才对 sproc 具有相同的性能,当执行计划存在时,这与正在创建的 sproc 不同。 TBH,我没有谈论参数化 SQL 查询。我谈论从客户端执行查询,例如sql = “Select * from Employees where Lastname = ‘” & ddl.selecteditem.text & “‘”
,而不是sql = “Select * from Employees where Lastname =@LastName”
@markzzz:至于存储过程的速度优势 - 请参阅我的答案。关键是:如果您始终发送不同的查询,则必须对每个查询进行分析,“编译”,然后执行-另外,您正在浪费(“污染”)计划缓存以及永远不会重用的执行计划- 再次损害您的整体系统性能
@markzzz:看看this other SO question 和它的答案——有很多很好的链接和示例,为什么简单的“转义单引号”是行不通的。还有see MSDN docs on SQL injection【参考方案7】:
与标准 SQL 语句不同,存储过程由数据库服务器编译和优化。此优化涉及使用有关存储过程执行时所需的特定数据库结构的信息。这种存储执行信息(执行计划)的过程非常节省时间,尤其是在存储过程被多次调用的情况下。
存储过程完全在数据库服务器上运行也提高了速度 - 无需通过网络传递大量 SQL 代码。对于一个简单的 SELECT 语句,这可能不会产生太大的影响,但在我们执行一系列循环和计算的情况下,它可能会产生显着的影响。
【讨论】:
这并不完全正确;正确参数化的查询被编译并存储在 SQL Server 上的计划缓存中 - 在执行速度/性能方面确实没有太大(如果有的话)差异。【参考方案8】:-
存储过程有时会运行得更快一些,因为或尽可能使用 RPC 调用
SP 对于必须重新编译的查询运行得更快 - 例如。 - 在中间某处使用临时表创建
【讨论】:
以上是关于为啥执行存储过程比脚本中的 SQL 查询更快?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 MS-Access 中的 Teradata 查询比 SQL Server 更快
存储过程EXECUTE IMMEDIATE V_SQL执行很慢或出不来,如果把V_SQL语句拿出来单独执行很快,这是为啥?