引言
本文旨在介绍使用存储过程封装应用程序所需的 Transact-SQL (T-SQL) 的主要优缺点,以便您对如何在您的环境中使用存储过程做出明智的决策。对于那些可以利用存储过程的人员,本文还介绍了与在 .NET 应用程序中使用存储过程相关的工具和最佳做法。
在这方面,大家的看法并不一致,有些人认为业务逻辑只应被编码到中间层或数据库中;有些人认为内联查询是唯一的选择;有些人则认为存储过程应用于任何情况。所有这些方法都具有优点和缺点。重要的是应考虑,对您的应用程序和环境至关重要的是什么。因此,让我们了解一下存储过程的概念,并考虑使用它们封装 T-SQL 代码的某些原因。
考虑使用存储过程的理由
也许您曾经在多处编写过使用 SqlCommand 对象的 T-SQL,但却从未考虑过是否有一个比将它并入数据访问代码更好的位置。由于应用程序随着时间的推移增添了一些功能,因此其内部可能包含一些复杂的 T-SQL 过程代码。存储过程为封装此代码提供了一个替换位置。
大多数人可能对存储过程已有所了解,但对于那些不了解存储过程的人员而言,存储过程是指一组作为单个代码单元一起存储于数据库中的 T-SQL 语句。您可以使用输入参数传入运行时信息,并取回作为结果集或输出参数的数据。存储过程在首次运行时将被编译。这将产生一个执行计划 - 实际上是 Microsoft® SQL Server™ 为在存储过程中获取由 T-SQL 指定的结果而必须采取的步骤的记录。然后,执行计划在内存中得到缓存,以备以后使用。这样会改善存储过程的性能,因为 SQL Server 无需为确定如何处理代码而重新分析它,而只需引用缓存的计划即可。这个缓存的计划一直可用,直到 SQL Server 重新启动,或直到它由于使用率较低而溢出内存。
性能
缓存的执行计划曾使存储过程较之查询更有性能优势。但对于 SQL Server 的几个最新版本,执行计划已针对所有 T-SQL 批处理进行了缓存,而不管它们是否在存储过程中。因此,基于此功能的性能已不再是存储过程的卖点。任何使用静态语法,且提交频率足以阻止执行计划溢出内存的 T-SQL 批处理将会获得同样的性能好处。“静态”部分是要害;任何更改,即使像添加注释这样无关紧要的更改,也将导致无法与缓存的计划相匹配,从而将无法重复使用计划。
但是,当存储过程可以用于降低网络流量时,它们仍然能够提供性能好处。您只需在网络中发送 EXECUTE stored_proc_name 语句,而非整个 T-SQL 例程,这可以在复杂操作中广泛使用。设计良好的存储过程可以将客户端与服务器之间的许多往返过程简化为单个调用。
此外,使用存储过程使您能够增强对执行计划的重复使用,由此可以通过使用远程过程调用 (RPC) 处理服务器上的存储过程而提高性能。使用 StoredProcedure 的 SqlCommand.CommandType 时,存储过程通过 RPC 执行。RPC 封装参数和调用服务器端过程的方式使引擎能够轻松地找到匹配的执行计划,并只需插入更新的参数值。
考虑使用存储过程提高性能时,最后要考虑是否要充分利用 T-SQL 的优点。请考虑要如何处理数据。
• 是否要使用基于集合的操作,或执行 T-SQL 中完全支持的其他操作?那么存储过程就是一个选择,而内联查询也可以使用。
• 是否尝试执行基于行的操作,或复杂的字符串处理?那么可能要重新考虑在 T-SQL 中进行这种处理,这不包括使用存储过程,至少要到 Yukon 发布并且公共语言运行库 (CLR) 集成可用后,才能使用存储过程。
可维护性和抽象
要考虑的另一个潜在优势是可维护性。理想情况下,数据库架构从不更改,业务规则不被修改,但在现实环境中,情况则完全不同。既然情况如此,那么假如可以修改存储过程以包括新 X、Y 和 Z 表(为支持新的销售活动而添加了这些表)中的数据,而不是在应用程序代码中的某个位置更改此信息,则维护对您来说可能比较轻易。在存储过程中更改此信息使得更新对应用程序而言具有透明性 - 您仍然返回相同的销售信息,即使存储过程的内部实现已经更改。更新存储过程通常比更改、测试以及重新部署程序集需要较少的时间和精力。
另外,通过抽象化实现并将此代码保存在存储过程中,任何需要访问数据的应用程序均可以获取一致的数据。您无需在多个位置维护相同的代码,用户便可获取一致的信息。
在存储过程中存储 T-SQL 的另一个可维护性优点是更好的版本控制。您可以对创建和修改存储过程的脚本进行版本控制,就像可以对任何其他源代码模块进行版本控制一样。通过使用 Microsoft Visual SourceSafe® 或某个其他源代码控制工具,您可以轻松地恢复到或引用旧版本的存储过程。
在使用存储过程提高可维护性时应值得注重的一点是,它们无法阻止您对架构和规则进行所有可能的更改。假如更改范围大到需要对输入存储过程的参数进行更改,或者要更改由其返回的数据,则您仍需要更新程序集中的代码以添加参数、更新 GetValue() 调用,等等。
要注重的另一个问题是,由于存储过程将应用程序绑定到 SQL Server,因此使用存储过程封装业务逻辑将限制应用程序的可移植性。假如应用程序的可移植性在您的环境中非常重要,则将业务逻辑封装在不特定于 RDBMS 的中间层中可能是一个更佳的选择。
安全性
考虑使用存储过程的最终原因是它们可用于增强安全性。
就治理用户对信息的访问而言,通过向用户授予对存储过程(而不是基础表)的访问权限,它们可以提供对特定数据的访问。您可以将存储过程看成是 SQL Server 视图(假如您对它们熟悉的话),除非存储过程接受用户的输入以动态更改显示的数据。
存储过程还可以帮助您解决代码安全问题。它们可以防止某些类型的 SQL 插入攻击 - 主要是一些使用运算符(如 AND 或 OR)将命令附加到有效输入参数值的攻击。在应用程序受到攻击时,存储过程还可以隐藏业务规则的实现。这对于将此类信息视为知识产权的公司非常重要。
另外,使用存储过程使您可以使用 ADO.NET 中提供的 SqlParameter 类指定存储过程参数的数据类型。这为验证用户提供的值类型(作为深层次防御性策略的一部分)提供了一个简单方法。在缩小可接受用户输入的范围方面,参数在内联查询中与在存储过程中一样有用。
使用存储过程增强安全性时值得注重的是,糟糕的安全性或编码做法仍然会使您受到攻击。对 SQL Server 角色创建和分配假如不加注重将导致人们访问到不应看到的数据。同时,假如认为使用存储过程便可防止所有 SQL 插入代码攻击(例如,将数据操作语言 (DML) 附加到输入参数),后果将是一样的。
另外,无论 T-SQL 位于代码还是位于存储过程中,使用参数进行数据类型验证都不是万无一失的。所有用户提供的数据(尤其是文本数据)在传递到数据库之前都应受到附加的验证。
存储过程对我是否适用?
或许适合吧。让我们概括一下它们的优点:
• 通过降低网络流量提高性能
• 提供单点维护
• 抽象化业务规则,以确保一致性和安全性
• 通过将某些形式的攻击降至最低,以增强安全性
• 支持执行计划重复使用
假如您的环境答应利用存储过程提供的好处(如上所述),强烈建议使用它们。对于改进数据在环境中的处理方式而言,它们提供了一个很好的工具。另一方面,假如您的环境中存在可移植性、大量使用非 T-SQL 友好的进程或者不稳定的数据库架构等削弱这些优点的因素,则您可能要考虑其他方法。
另一个要注重的事项是机构内部所拥有的 T-SQL 专业人员的数量。您有足够的 T-SQL 知识吗?您愿意学习吗?或者,您有 DBA 或合适的人员帮您编写存储过程吗?把握的 T-SQL 知识越多,存储过程就会越好,维护它们就会越轻易。例如,T-SQL 主要用于基于集合的操作,而不是基于行的操作。依靠于光标(因为它们向您提示数据集)将导致性能降低。假如您不太了解 T-SQL,请将本文作为一次学习机会。无论您将它用在何处,本文介绍的知识都将改善您的代码。
因此,假如您认为存储过程会为应用程序增添非凡的效果,请继续阅读本文。我们将回顾一些简化存储过程使用的工具,并了解一些创建存储过程的最佳做法。
Visual Studio .NET 工具
Microsoft Visual Studio® .NET 提供了一些工具,使您能够查看和操作 SQL Server 存储过程(以及其他数据库对象)。让我们简要介绍一下您可能期望获得的功能。
查看存储过程
您可以使用服务器资源治理器查看现有的存储过程、查看它们所需的参数或查看它们的内部实现。假如您已经连接到安装了 SQL Server 的服务器,则可以依次展开“ServerName”、“SQL Server”、“SQLInstanceName”、“Northwind”、“存储过程”,并展开“CustOrdersDetail”。该资源治理器将显示存储过程所需的任何参数及其返回的任何列。假如签出以上任何项的属性,便会发现数据类型表示为 ADO 类型。框架文档提供了这些类型与 .NET 类型之间的简单映射。当然,在 ADO.NET 代码中使用参数时,可能要将它们的数据类型表示为 SqlDBType 枚举的成员。您会看到 .NET 类型与 SqlDbType 类型之间的映射。
假如双击存储过程,Visual Studio 将会在 SQL 编辑器中打开它,其中显示了便于查看的颜色编码和所有内容。注重,它并不显示 CREATE PROCEDURE 语法(该语法实际上在数据库中已存在),由于它假定您要修改存储过程,因此提供了 ALTER PROCEDURE 语法。
创建和修改存储过程
您具有在相应