[sql杂谈]我们是否还一定应该使用存储过程

SQL是一种语言,用来声明在数据库上想要执行的操作(查询,更新或管理等)。数据库引擎得到的均是文本,就像C#源代码要由编译器处理一样,SQL源码也要必须通过某种方式的编译,以便生成一系列的底层数据库操作,这个输出就叫着执行计划。从概念角度考虑,生成执行计划的过程可以看做是程序编译的过程。
所谓的存储过程要比普通SQL有性能提升体现在对执行计划的重用上。换句话说,第一执行存储过程时,数据库将生成执行计划。然后执行代码。下一次执行的时候即可重用前面已经生成的执行计划,因此效率上会有提高。所有的SQL命令都需要执行计划。
这个(错误的)会议声明,数据库仅会重用存储过程的执行计划。不过在SQL Server和Oracle数据库(我们对其它产品也并不十分的了解)中,重要执行计划将会应用到任何SQL语句上。下面是SQL Server2005在线文档中的一段引用。
在SQL Server2005执行任何 SQL语句时,关系引擎首先查看缓存,判断其中是否有当前SQL的执行计划。SQL Server2005将重用任何可行的执行计划,以便减小重新编译SQL语句对性能上的影响。若没有找到现有的执行计划,SQL Server2005才会为当前查询生成新的执行计划。
因此,当前强调存储过程要比简单的SQL代码更高效的争论就没有任何意义了。从性能角度考虑,所有达到数据库的SQL代码都会被同等对待,编译之后,二者性能没有任何差别,这就是结论。
传言2:存储过程可以用来阻挡SQL注入攻击
这个传言和前面有关性能的传言有些相似。存储过程当然可以降低SQL注入发生的可能性,因为存储过程使用强类型参数。因此对于攻击者来说,很难在需要数字的地方传入字符串,反之亦然。
参数化查询也提供了同样的功能,例如,ADO.NET就对创建参数化查询提供了很好的支持,且ADO.NET被广泛应用于.NET平台上的很多数据库中,包括Entity Frameword,NHibernate和其它O/RM中。使用参数构造的SQL语句也可以和存储过程一样阻挡SQL注入攻击。
这里我们必须假设,读者都很了解存储过程,存储过程就是关系型数据库中定义的一个子集。随后,连接到该数据库的并提供了必要认识的用户即可以执行这个存储过程。
我们认为,这里的子程序一词是理解存储过程的知用范围和好处的关键,且这里我们所说的存储过程的适用范围和好处都是其目前的状态。我们承认,10年前人们会对存储过程有不同的看法,且总体来说更加倾向于使用。不过 ,软件领域中的10年是一段相当长的时间,相当于远古和现代。
维基百科上对于子程序的定义是一个大型程序中的一部分代码,用来执行专门的任务并从某种程序上与其它代码独立。在数据库编程中,子程序则表示将几个SQL语句组合起来,组成单一的一个复杂语句,即存储过程。这样调用者即可用统一的
接口
与数据库交互,易于保证安全,测试,优化和维护,其运行速度也会有所提升。
上述描述阐明了存储过程的优势和劣势。在不同的上下文中,前面提到的每个特征都可能成为优势或者劣势,这取决于你所创建的应用程序的需求和约束,以及逻辑的最终复杂性和处理复杂性的方式。同样,开发方式的不同也会对于存储过程成为优势或劣势产生影响。
客户的说,存储过程并不是万恶之源。不过对于那些使用现代观点,模式,工具和技术实现的带有一般复杂性的应用程序来说,存储过程被认为是一种过时的技术。我们的观点是存储过程并不是绝对不能使用,而是应该在必要也确定有所帮助时才使用。
在过去的10~15年中,编写系统的趋势是分层并依赖于真正的设计模式,不过仍有一个地方被很多人忽视。人们仍旧经常将业务逻辑放在存储过程中实现,让两个完全不同的层的功能混在一起。你会很容易地不小心将某段业务逻辑放在存储过程中,这样做真的合理吗?以后的几章就列举一些长久以来一直听到的编写复杂存储过程的理由,其中有些理由在10年前可能更加合理一些,不过今天事情已经发生了很多变化。因此,我们在以后几章中介绍一些常见的有关储过程的传言并进行分析,当然,其中会根据现在和以前的技术作出对比分析。
传言3:存储过程要比 SQL代码更加安全
在执行任何SQL语句之前,数据库引擎都会尝试匹配调用者提供的论证信息和所请求资源的访问权限。根据匹配结果,引擎决定是否执行该SQL代码。
这样看来,从安全角度存储过程显然要比普通的SQL代码更有优势。为什么会这样呢?因为存储过程可看做是数据库中的一个实体,可以由数据库管理员DBA显式给其安全需求,即借助数据库的安全基础设施来保护存储过程,因为存储过程本来就是属于数据库资源的一部分。
而普通的SQL语句就是个字符串,将动态地发送给数据库执行,因此数据库引擎不能将其当成内部资源,也无法把权限关联在它上面。权限仅能应用在SQL将使用的数据表或视图上,这显然属于另一个层次的安全。因此,操作整体上的安全性要交给调用者负责保证。
大多数人应该对上面的分析没有疑义,关键是通过上面的分析可以产生两个相反的结论,且这取决于人们的态度,技能和看待问题的角度。
若你更熟悉数据库,那么通常会停在这里并得出结论:存储过程是设计数据访问层中必不可少的。
不过问问自己这3个问题:存储过程中将要执行哪些类型的操作?这些操作都要带有什么样的签名?在大型系统中你实际需要多少个存储过程?
在我们看来,若你觉得存储过程中必不可少的,那么也会不自觉地在存储过程中实现(至少一部分)业务逻辑,但这一点是我们提倡应该竭力避免的。稍后将继续讨论这个问题,这里我们先来完成对安全性的讨论。
安全性是个横切的关注点,应该在从表现层到数据库的各层中都有处理。今天,基于角色的安全是最灵活且有效的做法。在基于角色的安全模型中,我们对安全性有着双重的保证,第一重是中间层中使用基于角色的安全,第二重是数据库引擎中的声明安全。且这一点和使用动态SQL代码或存储过程并不相关。
若使用存储过程来实现系统的安全性需求,则会不自觉地将数据库开发人员和其它开发人员分离开来。而前面曾提到过,安全性应该是个团队的工作。因些我们就回到了那些倾向于使用存储过程的说法中———“若想保证安全的话,就要使用存储过程”。不过实际上,若你真的想要保证安全,则必须放弃将存储过程作为必不可少的功能的想法。之所以这样说,并不是因为存储过程有什么不好,而是因为若将存储过程作为整个系统的核心,那么必定会让人作出不好的设计决定———从安全角度考虑。你仍然可以使用存储过程,不过不要再说是出于安全性考虑,也不要用存储过程来实现逻辑。那存储过程还有什么用呢?一般也仅用在处理数据表访问上。
传言4:存储过程可以让SQL代码更加稳定且不易改变
软件领域中有很多让人孜孜不倦追求的理想目标,其中一个就是使编程可以不受到数据库模型的变化影响。若某个数据库表发生了变化,会不会影响到其相关的代码吗?这是一个常见的疑虑。不过正确的答案仍然不是使用存储过程。
若将SQL命令放在数据访问层中(就像我们在手工数据访问层中的实现一样),就会在代码和物料数据模型之间建立依赖。若数据模型有所变化,就需要更新代码。不过我们可以说,此类依赖仅仅存在于数据映射器类中,且SQL语句也是作为私有成员声明的。因此虽然存在依赖,不过范围非常小。若数据表发生了变化,必须更新数据映射类。不过这也就是所有需要的修改,仅限于数据访问层内部。
使用存储过程是不是真的能让代码与数据模型的变化完全独立呢?若数据模型发生了变化,那么要实现两种更新:修改SQL代码和修改调用者代码。其实,我们并没有看到在数据映射器类中调用存储过程和普通SQL语句有什么区别。
在我们看来,存储过程可以让数据访问代码更加稳定的说法仅仅是个传言,这个传言是以前的那种系统设计方式的自然产物,即数据库开发人员和其他开发人员基本没有沟通交流。
若你真想完全不依赖于物理数据模型,那么应该选择领域驱动设计,并将数据库设计成一个简单的持久化层。数据访问代码将由O/R映射层负责以参数化查询(甚至是以存储过程)的方式动态生成。