为.net开发者提供的一份关于存储过程的评论(转)
序言
本文的主要目的是根据应用程序的需要,介绍一些将T-SQL语句封装入存储过程时的关键性要素,以便当你在自己的系统中应用他们时可以做出明智的决定。我将介绍一些工具和较好的练习给那些希望在.net应用程序中利用它们的开发人员。
我很清楚,我现在介入了一场类似不同宗教之间的斗争,一场关于事务逻辑应该仅仅建立在中间层还是仅仅建立在数据层的斗争;或者说是仅仅在程序中编写查询代码还是全部采用存储过程的争论。当然,这两种方法各有利弊。重要的是我们要考虑什么对我们的程序和系统环境比较重要。所以,让我们来看看存储过程到底是什么,并考虑我们用它来封装T-SQL语句的原因是什么。
为何考虑存储过程?
也许你经常在程序中利用SqlCommand对象和编写T-SQL语句来完成数据操作,但从没有考虑过寻找更好的地方来存放这些T-SQL语句,而不是将他们与你的数据访问代码混合在一起。也许随着时间的流逝,你的应用程序的功能不断增加,造成了其中存在很多复杂的T-SQL语句代码。存储过程是一种封装这些复杂语句的更好的方法。
也许大多数人都对存储过程有所了解,但对于那些不了解它的人,存储过程就是一组许多预先编写好的T-SQL语句序列,并作为一个独立的代码单元保存在数据库中。你可以通过输入参数给它传送运行时信息,也可以通过结果集或输出参数获取返回数据。当存储过程第一次运行时,它就被编译了。它产生了一个执行序列,实质上是一条记录,记录了在存储过程中指定的、用以获得结果的T-SQL语句的执行步骤。之后,这个执行序列便被保存在缓存中以备后用。这将提高存储过程的性能,因为当再一次执行时,存储过程不需要分析代码去执行任务,而是简单地转向保存在缓存中的执行序列。缓存中的内容在SQL Server重新启动之前和在它的内存生命期内都一直有效,它的内存生命期取决于它的最低内存消耗。
性能
在查询时,这些缓存中的执行序列可以提高存储过程的性能。然而,在最近的两个SQL Server版本中,所有的T-SQL语句批处理都被以执行序列的形式保存在缓存中,而不管它是否存在于存储过程中。因此,在基于此特性上的性能的提高不再是存储过程的卖点。任何采用静态(不经常改变)语法的T-SQL批处理被频繁地提交,以防止它在缓存中的执行序列因为超过内存生命期而消失,这样做同样可以得到很高的性能。注意这里“静态”很 关键,因为即使无关紧要的变化(例如注释的改变)都会导致不与缓存中的执行序列进行匹配。从而也就导致了执行序列的无法重用。
可是,存储过程仍旧有它的优点,利用它可以减少网络的数据传输量。你只需要发送如下的指令:
EXECUTE stored_proc_name便可以轻松地执行一系列复杂的操作,而不是那些传统的冗长的T-SQL语句。一个设计良好的存储过程可以显著减少客户端与服务器端的往返通讯,甚至可以压缩至一次调用。
另外,使用远程调用(RPC)服务器端的存储过程可以提高执行序列的重用性,从而提高性能。当你使用一个CommandType为StoredProcedure的SqlCommand对象时,存储过程将通过远程调用(RPC)被执行。远程调用排列好参数并调用服务器端的存储过程,它使服务器引擎很容易找到与之匹配的执行序列并方便地提供已经更新的参数值。最后一件需要考虑的事情是,当你准备应用存储过程提高程序的性能时你是否充分发挥了T-SQL的力量。考虑一下你打算怎样操作这些数据。
可维护性与共性提取
第二个值得考虑的潜在好处是可维护性。在理想的情况下你的数据库架构不会改变,你的事务规则也不会改变;但在现实中这些是经常发生的。例如:当需要为新的促销活动添加x,y,z表时,你可以通过修改存储过程简单地将这些数据包含近来,而不需要再到你的程序代码中到处寻找需要修改的语句。在存储过程中修改代码对于应用程序来说是透明的,你依旧可以得到相同的销售信息,虽然存储过程的内部实现已经改变了。相对于你修改现有代码,重新测试和配置程序来说,直接修改存储过程更加省时省力。
而且,通过提取一些相同的实现方法并将其保存在存储过程中,当应用程序需要通过这些方法访问数据时只需采用相同的操作。你便不用维护散布在许多地方的相同代码了,并保证你的用户可以获得一致的信息。
另外一个对维护有好处的是,将T-SQL语句保存在存储过程中也是一种较好的控制版本的方法。你可以利用那些创建和修改存储过程的脚本来做版本控制,就像控制源代码模块的版本一样。通过使用微软提供的Visual SourceSafe和其它一些版本控制工具,你可以很容易地引用和恢复到旧的版本。
请不要误解,使用存储过程来提高可维护性并不能避免需要修改数据架构和事务规则的可能。当这些改变过大时,就需要更改存储过程的输入参数与返回值,而且要修改你的前台程序代码例如添加参数、更新GetValue()函数的调用等等。
另外需要考虑的问题是,使用存储过程封装事务逻辑限制了你应用程序的可移植性,那样便被束缚在SQL Server上了。如果可移植性对于你的系统环境来说制关重要的话,那么将事务逻辑封装在与关系数据库无关的中间层中是个不错的选择。
安全性
使用存储过程的最后一个原因就是它可以提高系统的安全性。
在规范用户访问信息方面,它可以提供给用户经过授权的指定信息而不是那些非授权的表的内容。除了存储过程可以接受用户输入而动态改变数据显示的功能外,你可以将他想象为SQL Server中的视图(如果你对视图比较熟悉的话)。
它还可以帮助你逃离代码的安全隐患。保护你免受一种叫做‘SQL注射’的攻击,这种攻击主要会在你的合法输入参数中添加如AND 或者 OR的操作符。存储过程还会隐藏事务逻辑的实现以减少你的应用程序泄密的危险。因为事务逻辑非常重要,这种信息被认为是知识产权。
除此之外,使用存储过程时,你可以利用ADO.net提供的SqlParameter类,为存储过程提供数据类型正确的参数。这也为我们提供了一种用来验证用户输入参数的方法,同时也是深度防范策略的一部分。注意,在单独的查询语句中,参数同样可以起到缩小用户输入范围的作用。
但是也请注意,使用存储过程来提高安全性时,如果不作好安全设置以及没有良好的编码习惯,仍旧会使你暴露在进攻之下。在创建SQL Server角色和分配权限时,如果不注意就会导致用户访问到他们不该看到的数据。不要绝对的认为使用存储过程可以彻底避免''SQL注射''攻击,在输入参数后面追加一些数据操纵语言(DML)一样可以进行攻击。
因此,使用参数来验证输入数据的类型也不是十分稳妥,无论是在一条T-SQL语句中还是在存储过程中,所有用户输入的数据,尤其是那些文本数据,应该在传送到数据库之前采用额外的验证。
存储过程适合我么?
哦,也许是的。让我们回顾一下它的优点:
- 可以减少网络传输数据量以提高性能
- 提供方便的单点维护
- 将事务规则提取出来以提高一致性和安全性
- 减少通过输入表单进行的攻击以提高安全性
- 加强执行序列的重用性
另外一个需要考虑的事情是你所掌握的T-SQL技术的程度。你有足够多的T-SQL知识么,或者你是否愿意学习?要么你有一位数据库管理员(DBA)或有与其相当技术的高手可以耐心地为你写存储过程。你所掌握的T-SQL知识越多,你的存储过程就运行的越好,并且很少会让你费心去维护。例如T-SQL基于数据集的操作比基于数据行的操作性能要好。因此避免使用游标,它会使你的数据库性能下降。如果你对T-SQL不是很了解,那么请认为这是一次很好的学习机会。这些知识将会改进你的代码,而无论你在哪里编写它们。
所以,对于相信存储过程肯定可以给你的程序带来一些好处的人,请你读下去。我们将会看到一些使它工作起来更容易的工具,并学习一些怎样创建它的例子。
![](http://www.vckbase.com/document/image/paragraph.gif)
Microsoft Visual Studio.NET 提供了一些让你查看和操作SQL Server存储过程的工具(同样还支持其它数据库)。让我们快速浏览一下你所期待的功能。
查看存储过程
你可以使用服务器资源管理器来查看已经存在的存储过程,看看它需要哪些参数或者看看它们的内部实现。如果你已经连接上了一个安装了SQL Server数据库的服务器,你就可以展开以下节点:服务器名->SQL Servers->服务器实例名->Northwind->存储过程,并最后展开CustOrdersDetail。管理器将显示此存储过程所需要的所有参数以及其返回的列。如果你查看这些列的属性,你会注意到这些数据类型被表示为ADO类型。.NET框架的文档为我们提供了一份轻便的手册,上面描绘了ADO类型与.NET数据类型之间的映射关系。当然,在你的ADO.net代码中使用参数时,你应该用SqlDbType的枚举值来表示这些参数的数据类型。你可以参考.NET数据类型与SqlDbType类型之间的映射关系。
如果你双击你想查看的存储过程,Visual Studio将会在SQL编辑器中打开它,并在代码上标记不同的颜色。注意在代码的头部显示的不是CREATE PROCEDURE语句(事实上它确实存在),取而代之的是 ALTER PROCEDURE 语句,因为系统会自动认为你想要更改这个存储过程。
创建和修改存储过程
在你要创建存储过程的数据库中,你是否有足够的权限呢?如果没有,那么获取权限,这是你创建和修改数据库的第一步。如果需要帮助的话,请联系你的数据库管理员。
你可以通过在服务器资源管理器的【存储过程】结点上点击鼠标右键并选择【新建存储过程】来创建一个新的存储过程(在其他存储过程名上面点击右键也可以)。随后会弹出一个SQL编辑器,其中已经提供了CREATE PROCEDURE语句帮助你开始编写,之后你可以键入你的存储过程体。修改存储过程同查看的操作一样:先在服务器资源管理器中定位存储过程,之后将其打开。
如果你需要系统帮助你在存储过程中创建复杂的查询语句,那么在存储过程编辑窗口中单击鼠标右键,在弹出菜单中选择【插入SQL】。除此之外,你也可以先选择一个语句块,然后单击鼠标右键,在弹出菜单中选择【设计SQL块】。这两个操作都会打开一个查询创建窗口,它为你提供了一个图形界面用来创建和修改T-SQL语句。当你设计好后,你可以将其剪切并粘贴到你的存储过程中。 不幸的是,此编辑器没有提供关键字的智能感知系统,所以请准备好将SQL Server在线帮助打开以便参考。当保存存储过程时,系统会警告你那里发生了语法错误需要修改。注意,在错误完全改好之前是无法保存的,因此,你可能需要在编写代码之前确定完成它的时间。你可以先编写一段代码然后在SQL Server查询分析器中运行检查,反复这样,但这是另一篇文章的内容了。
一旦你编写好了你的存储过程,你就可以点击鼠标右键并选择【运行存储过程】来测试你的这个存储过程了。
![](http://www.vckbase.com/document/image/paragraph.gif)
如果你开始为你的应用程序创建存储过程,这里有一些小技巧,请记住它们,这会使你的存储过程较好的运行并与其它程序密切配合。
使用SET NOCOUNT ON 语句
在默认设置中存储过程会返回SQL语句执行所影响的行数。如果在你的程序中不需要这些信息(大多数程序都不会需要),可以使用SET NOCOUNT ON语句来停用此项功能。跟据你的存储过程中影响数据行的语句的多少,这么做可以减少服务器与客户端的信息往返次数。这也许算不上什么大问题,但在一个数据传输负载较高的程序中它便会明显降低性能。
create procedure test_MyStoredProc @param1 int不要使用SP_前缀 SP_前缀是为系统级的存储过程所保留的。数据库引擎总是首先在master数据库中寻找有SP_前缀的存储过程。这就意味着首先会在master数据库中搜寻一遍,之后才会到达需要执行的存储过程那里,这就会花掉一些时间。更糟的是,如果在系统级的存储过程中存在与你的存储过程同名的,那你的存储过程将永远不会被执行。
as
set nocount on
尽量少使用可选参数
在大量使用可选参数时请考虑清楚。如果你执行存储过程时为其输入了一组不需要的参数,这种额外的工作会影响到存储过程的性能。在你使用条件控制编码以获得不同的参数组合时你会遇到这种得问题,而这会浪费你的时间并增加你出错的机会 。
需要时使用输出参数
你可以使用输出参数来返回所需要的数据,这可以加快你的开发速度并减少处理的麻烦。当你的程序需要返回一个单值时,你可以采用这个方法来取代传统的实例化一个结果集的方法。你还可以在适当的时候通过输出参数返回一个游标,我们将在以后的文章深入讨论游标与基于数据集的操作。
提供一个返回值
使用存储过程的返回值,将处理的状态信息返回给调用此存储过程的应用程序。在你的开发小组内,标准化一系列的返回值及它们所代表的意义,并统一使用它们。这样做不仅可以更容易地处理调用中发生的错误,而且还可以为你的最终用户提供有用的信息以解决他们的问题。
先写DDL,再写DML
在存储过程中当数据操纵语言(DML)先于数据定义语言(DDL)被执行时,SQL Server将会重新编译存储过程,因为数据操纵语言引用了被数据定义语言改变了的对象。发生这种情况是因为,SQL Server需要统计由数据定义语言导致的对象改变,用来创建数据操纵语言的执行计划。如果你在一开始便计划好了数据定义语言,那么只需要重新编译一次。如果你将两者混合写在一起,将会强迫存储过程重新编译多次,而这将极大影响程序的性能。
永远要写注释
你也许不会再维护这些代码。但是可以肯地说,将来其他人会来维护,他们需要知道代码的功能。
![](http://www.vckbase.com/document/image/paragraph.gif)
存储过程是否对你有用,我希望你已经有所感觉。它是SQL Server免费提供的工具,所以当它对你的应用程序环境和维护有所帮助时,你确实应该采用它。以上我提供的介绍信息可以帮助你开始使用存储过程。SQL Server在线参考手册,Visual Studio文档,当然还有MSDN上那些深入的信息可以推动你继续前进。