114

传统观点认为存储过程总是更快。因此,由于它们总是更快,因此请始终使用它们

我很确定这是基于某种历史背景,而这曾经是这种情况。现在,我并不主张不需要存储过程,但我想知道在 MySQL、SQL Server、Oracle 或 < Insert_your_DB_here > 等现代数据库中,在什么情况下需要存储过程。通过存储过程进行所有访问是否过大?

4

20 回答 20

267

请注意,这是对未受特定 DBMS 监管的存储过程的一般看法。一些 DBMS(甚至是同一 DBMS 的不同版本!)可能与此相反,因此您需要在假设所有这些仍然存在之前与您的目标 DBMS 进行仔细检查。

近十年来,我一直是 Sybase ASE、MySQL 和 SQL Server DBA(以及 C、PHP、PL/SQL、C#.NET 和 Ruby 中的应用程序开发)。所以,在这场(有时)圣战中,我没有什么特别需要磨的。

存储过程的历史性能优势通常来自以下(无特定顺序):

  • 预解析的 SQL
  • 预生成的查询执行计划
  • 减少网络延迟
  • 潜在的缓存优势

预解析的 SQL - 与编译代码和解释代码类似的好处,除了在非常微观的层面上。

还是优势? 在现代 CPU 上根本不是很明显,但是如果您发送一个非常大的 SQL 语句,每秒 110 亿次,则解析开销可能会增加。

预先生成的查询执行计划。如果您有许多 JOIN,则排列可能会变得非常难以管理(现代优化器出于性能原因有限制和截止)。由于优化器试图找出“近乎最佳”的延迟,非常复杂的 SQL 具有明显的、可测量的(在我们调整 DBMS 之前,我已经看到一个复杂的查询需要 10 多秒才能生成计划)延迟并不是未知的“执行计划。存储过程通常会将其存储在内存中,这样您就可以避免这种开销。

还是优势? 大多数 DBMS(最新版本)将缓存 INDIVIDUAL SQL 语句的查询计划,从而大大减少存储过程和 ad hoc SQL 之间的性能差异。有一些注意事项和情况并非如此,因此您需要在目标 DBMS 上进行测试。

此外,越来越多的 DBMS 允许您提供优化器路径计划(抽象查询计划)以显着减少优化时间(针对即席和存储过程 SQL !!)。

警告缓存查询计划不是性能灵丹妙药。有时生成的查询计划不是最佳的。例如,如果您发送SELECT * FROM table WHERE id BETWEEN 1 AND 99999999,DBMS 可能会选择全表扫描而不是索引扫描,因为您正在抓取表中的每一行(统计数据如此)。如果这是缓存版本,那么稍后发送 SELECT * FROM table WHERE id BETWEEN 1 AND 2. 这背后的原因超出了本文的范围,但进一步阅读请参阅: http: //www.microsoft.com/technet/prodtechnol/sql/2005/frcqupln.mspxhttp://msdn.microsoft.com/ en-us/library/ms181055.aspxhttp://www.simple-talk.com/sql/performance/execution-plan-basics/

“总而言之,他们确定在执行编译或重新编译时提供除公共值之外的任何值会导致优化器为该特定值编译和缓存查询计划。然而,当该查询计划被重用于后续执行相同的值时查询公共值('M','R'或'T'),它导致次优性能。这个次优性能问题一直存在,直到重新编译查询。那时,基于@P1提供的参数值,查询可能有也可能没有性能问题。”

减少网络延迟 A) 如果您一遍又一遍地运行相同的 SQL——并且 SQL 增加了许多 KB 的代码——用简单的“exec foobar”替换它确实可以增加。B) 存储过程可用于将过程代码移动到 DBMS。这节省了将大量数据转移到客户端的过程,只是让它返回少量信息(或根本没有!)。类似于在 DBMS 中与在您的代码中进行 JOIN(每个人都喜欢的 WTF!)

还是优势? A) 现代 1Gb(以及 10Gb 及以上!)以太网确实可以忽略不计。B) 取决于您的网络的饱和程度——为什么无缘无故地来回推送几兆字节的数据?

潜在的缓存优势 如果您在 DBMS 上有足够的内存并且您需要的数据在服务器的内存中,那么执行服务器端数据转换可能会更快。

还是优势? 除非您的应用程序对 DBMS 数据具有共享内存访问权限,否则边缘将始终是存储过程。

当然,如果不讨论参数化 SQL 和即席 SQL,则任何关于存储过程优化的讨论都是不完整的。

参数化/准备好的 SQL
是存储过程和即席 SQL 之间的一种交叉,它们是宿主语言中的嵌入式 SQL 语句,使用“参数”作为查询值,例如:

SELECT .. FROM yourtable WHERE foo = ? AND bar = ?

这些提供了更通用的查询版本,现代优化器可以使用它来缓存(和重用)查询执行计划,从而获得存储过程的大部分性能优势。

Ad Hoc SQL 只需打开 DBMS 的控制台窗口并输入 SQL 语句。在过去,这些是“最差”的执行者(平均而言),因为 DBMS 无法像参数化/存储的 proc 方法那样预先优化查询。

还是劣势? 不必要。大多数 DBMS 有能力将临时 SQL “抽象”成参数化版本——因此或多或少地否定了两者之间的差异。有些会隐式执行此操作,或者必须使用命令设置启用(SQL 服务器: http: //msdn.microsoft.com/en-us/library/ms175037.aspx,Oracle :http : //www.praetoriate.com/oracle_tips_cursor_sharing。 .htm)。

得到教训? 摩尔定律继续向前发展,DBMS 优化器随着每个版本的发布都变得更加复杂。当然,您可以将每一条愚蠢的 SQL 语句放在存储过程中,但要知道从事优化器工作的程序员非常聪明,并且一直在寻找提高性能的方法。最终(如果还没有的话)即席 SQL 性能将变得与存储过程性能无法区分(平均而言!),因此任何类型的大规模存储过程都使用**仅出于“性能原因”**对我来说听起来像是过早的优化.

无论如何,我认为如果您避免边缘情况并使用相当普通的 SQL,您将不会注意到 ad hoc 和存储过程之间的区别。

于 2008-09-12T20:53:57.390 回答
29

使用存储过程的原因:

  • 减少网络流量——您必须通过网络发送 SQL 语句。使用sprocs,可以批量执行SQL,效率也更高。
  • 缓存查询计划——第一次执行 sproc 时,SQL Server 创建一个执行计划,该计划被缓存以供重用。这对于频繁运行的小型查询特别有效。
  • 使用输出参数的能力——如果你发送返回一行的内联 SQL,你只能返回一个记录集。使用 sprocs,您可以将它们作为输出参数取回,这要快得多。
  • 权限——当你发送内联 SQL 时,你必须向用户授予对表的权限,这比仅仅授予执行存储过程的权限要多得多
  • 逻辑分离——删除生成 SQL 的代码并将其隔离在数据库中。
  • 无需重新编译即可编辑的能力——这可能会引起争议。您可以在存储过程中编辑 SQL,而无需重新编译应用程序。
  • 查找表的使用位置——使用 sprocs,如果要查找引用特定表的所有 SQL 语句,可以导出 sproc 代码并搜索它。这比试图在代码中找到它要容易得多。
  • 优化——当使用存储过程时,DBA 更容易优化 SQL 和调整数据库。更容易找到丢失的索引等。
  • SQL 注入攻击——正确编写的内联 SQL 可以防御攻击,但存储过程更适合这种保护。
于 2008-10-16T15:36:27.980 回答
21

在许多情况下,存储过程实际上更慢,因为它们更一般化。虽然存储过程可以进行高度调整,但根据我的经验,有足够的开发和制度摩擦,一旦它们工作,它们就会留在原地,所以存储过程通常倾向于“以防万一”返回很多列 - 因为你不每次更改应用程序时都希望部署一个新的存储过程。另一方面,OR/M 只请求应用程序正在使用的列,这减少了网络流量、不必要的连接等。

于 2008-09-12T20:38:25.663 回答
9

这是一场持续不断的辩论(例如,这里)。

编写糟糕的存储过程就像在应用程序中编写糟糕的数据访问逻辑一样容易。

我的偏好是存储过程,但那是因为我通常在企业环境中使用非常大型和复杂的应用程序,其中有专门的 DBA 负责保持数据库服务器的正常运行。

在其他情况下,我很高兴使用 LINQ 等数据访问技术来进行优化。

不过,纯粹的性能并不是唯一的考虑因素。安全和配置管理等方面通常至少同样重要。

编辑:虽然 Frans Bouma 的文章确实很冗长,但它在安全性方面错过了一点。它已有 5 年历史的事实也无助于它的相关性。

于 2008-09-12T20:41:20.133 回答
9

在大多数现代数据库上,存储过程与参数化或准备好的查询没有明显的速度差异,因为数据库还将缓存这些查询的执行计划。

请注意,参数化查询与 ad hoc sql 不同。

imo 今天仍然支持存储过程的主要原因更多地与安全性有关。如果您专门使用存储过程,您可以禁用应用程序用户的 INSERT、SELECT、UPDATE、DELETE、ALTER、DROP 和 CREATE 等权限,只保留 EXECUTE。

这提供了一些额外的保护来防止二阶sql 注入。参数化查询仅防止一阶注入。

于 2008-09-12T20:50:34.730 回答
5

显然,实际表现应该在个别情况下衡量,而不是假设。但即使在性能受到存储过程影响的情况下,也有充分的理由使用它们:

  1. 应用程序开发人员并不总是最好的 SQL 编码员。存储过程对应用程序隐藏 SQL。

  2. 存储过程自动使用绑定变量。应用程序开发人员经常避免绑定变量,因为它们看起来像是不需要的代码,并且在小型测试系统中几乎没有什么好处。稍后,未能使用绑定变量会限制 RDBMS 的性能。

  3. 存储过程创建了一个间接层,以后可能会有用。无需接触应用程序代码即可在数据库端更改实现细节(包括表结构)。

  4. 创建存储过程的练习对于记录系统的所有数据库交互很有用。当事情发生变化时,更容易更新文档。

也就是说,我通常在我的应用程序中使用原始 SQL,以便我可以自己控制它。这取决于您的开发团队和理念。

于 2008-09-12T20:51:06.583 回答
5

尚未有人提到存储过程的好处的一个主题是安全性。如果您通过存储过程专门使用数据访问来构建应用程序,则可以锁定数据库,以便唯一的访问是通过这些存储过程。因此,即使有人获得了数据库 ID 和密码,他们也将受到限制,他们可以查看或针对该数据库执行的操作。

于 2008-09-13T14:59:28.950 回答
4

2007 年,我在一个项目中,我们通过 ORM 使用 MS SQL Server。我们有 2 个不断增长的大表,在 SQL Server 上加载时间长达 7-8 秒。在制作了 2 个大型存储 SQL 过程并通过查询计划器对其进行优化后,每个 DB 加载时间降至不到 20 毫秒,因此显然仍然存在使用存储 SQL 过程的效率原因。

话虽如此,我们发现存储过程最重要的好处是增加了维护便利性、安全性、数据完整性,以及将业务逻辑与中间件逻辑解耦,使所有中间件逻辑受益于这两个过程的重用.

我们的 ORM 供应商通常声称触发许多小型 SQL 查询将比获取大型连接数据集更有效。我们的经历(令我们惊讶的是)显示了其他东西。

当然,这可能因机器、网络、操作系统、SQL 服务器、应用程序框架、ORM 框架和语言实现而异,因此衡量任何收益,您认为您可能会从做其他事情中获得收益。

直到我们进行基准测试,我们才发现问题出在 ORM 和承担所有负载的数据库之间。

于 2009-11-25T14:55:48.353 回答
3

我更喜欢在有意义的情况下使用 SP。无论如何,在 SQL Server 中,SP 对参数化查询没有性能优势。

然而,在我目前的工作中,我的老板提到我们被迫使用 SP,因为我们的客户需要它们。他们觉得他们更安全。我来这里的时间还不够长,无法查看我们是否正在实施基于角色的安全性,但我有一种感觉。

因此,在这种情况下,客户的感受胜过所有其他论点。

于 2008-09-12T20:45:03.870 回答
2

对我来说,存储过程的一个优点是与宿主语言无关:您可以从 C、Python、PHP 或任何应用程序切换到另一种编程语言,而无需重写代码。此外,诸如批量操作之类的某些功能确实可以提高性能,并且在宿主语言中不容易获得(根本不是?)。

于 2011-10-11T16:20:18.690 回答
1

我不知道他们更快。我喜欢使用 ORM 进行数据访问(不要重新发明轮子),但我意识到这并不总是一个可行的选择。

Frans Bouma 有一篇关于这个主题的好文章:http ://weblogs.asp.net/fbouma/archive/2003/11/18/38178.aspx

于 2008-09-12T20:34:44.517 回答
1

阅读 Frans Bouma 的精彩文章(如果有点偏颇)。

于 2008-09-12T20:35:26.030 回答
1

我只能说 SQL 服务器。在那个平台上,存储过程很可爱,因为服务器存储了执行计划,这在大多数情况下可以大大提高性能。我说“在大多数情况下”,因为如果 SP 具有广泛不同的执行路径,您可能会获得次优的性能。然而,即使在这些情况下,对 SP 进行一些明智的重构也可以加快速度。

于 2008-09-12T20:39:05.460 回答
1

为 CRUD 操作使用存储过程可能是大材小用,但这将取决于使用的工具和您自己的偏好(或要求)。我更喜欢内联 SQL,但我确保使用参数化查询来防止 SQL 注入攻击。我保留了这部xkcd 漫画的印刷品,以提醒您如果不小心可能会出现什么问题。

当您处理多组数据以返回单组数据时,存储过程可以带来真正的性能优势。在存储过程中处理数据集通常比通过线路发送它们以在客户端处理更有效。

于 2008-09-12T21:01:47.883 回答
1

意识到这个问题有点离题,但是如果您使用大量存储过程,请确保有一种一致的方式将它们置于某种源代码控制(例如,颠覆或 git)之下,并且能够将更新从您的开发系统迁移到测试系统再到生产系统。

当这是手工完成时,无法轻松审核代码在哪里,这很快就会变成一场噩梦。

于 2008-09-13T14:51:53.210 回答
0

存储过程非常适合经常运行 SQL 代码的情况,因为数据库将其存储在内存中。如果您在存储过程之外重复运行相同的代码,您可能会因为数据库一遍又一遍地重新解析相同的代码而导致性能下降。

我通常将代码称为存储过程或 SqlCommand (.NET) 对象,并根据需要多次执行。

于 2008-09-12T20:34:57.450 回答
0

是的,它们大部分时间都更快。SQL 组合也是一个巨大的性能调优领域。如果我正在做一个后台类型的应用程序,我可能会跳过它们,但我肯定会使用它们,因为其他人也说过的所有原因......即安全性。

于 2008-09-12T21:03:08.223 回答
0

恕我直言...

将“C_UD”操作限制为存储过程可以将数据完整性逻辑保持在一个位置。这也可以通过将“C_UD”操作限制到单个中间件层来完成。

可以向应用程序提供读取操作,以便它们可以仅连接所需的表/列。

于 2008-09-13T16:03:53.990 回答
0

也可以使用存储过程来代替参数化查询(或临时查询),以获得其他一些优势:

  • 如果您需要更正某些内容(排序顺序等),则无需重新编译您的应用程序
  • 您可以拒绝访问该用户帐户的所有表,仅授予对存储过程的访问权限,并通过存储过程路由所有访问权限。这样,您可以对所有输入进行自定义验证,比表约束更灵活。
于 2008-09-13T22:50:34.213 回答
-1

减少网络流量——SP 通常比动态 SQL 更差。因为人们不会为每个选择创建一个新的 SP,如果你只需要一个列,你会被告知使用包含他们需要的列的 SP,而忽略其余的列。获得一个额外的专栏,并减少您刚刚离开的网络使用量。此外,当使用 SP 时,您往往会有很多客户端过滤。

缓存——MS-SQL 没有区别对待它们,因为 MS-SQL 2000 可能是 7,但我不记得了。

权限——这不是问题,因为我所做的几乎所有事情都是 Web 或有一些中间应用程序层来完成所有数据库访问。我使用的唯一可以直接访问数据库的软件是第 3 方产品,这些产品专为用户直接访问而设计,并且基于授予用户权限。是的,MS-SQL 权限安全模型糟透了!!!(还没有花时间在 2008 年)作为最后一部分,希望看到有多少人仍在进行直接客户端/服务器编程与 Web 和中间应用程序服务器编程的调查;如果他们正在做大型项目,为什么没有 ORM。

分离——人们会质疑你为什么将业务逻辑放在中间层之外。此外,如果您正在寻找分离数据处理代码的方法,则无需将其放入数据库即可。

编辑能力——您无需担心什么测试和版本控制?也只是客户端/服务器的问题,在网络世界中没有问题。

查找表——只有你能识别出使用它的SP,才会坚持使用版本控制系统、agent ransack或visual studio等工具来查找。

优化——您的 DBA 应该使用数据库工具来查找需要优化的查询。数据库可以告诉 DBA 哪些语句占用的时间和资源最多,他们可以从那里修复。对于复杂的 SQL 语句,如果简单的选择不担心,程序员应该被告知与 DBA 交谈。

SQL 注入攻击——SP 没有提供更好的保护。他们唯一得到认可的是,他们中的大多数人使用参数与动态 SQL 进行教学,大多数示例都忽略了参数。

于 2008-10-24T16:44:32.727 回答