想想我们的项目里能不能用存储过程。
不能:因为项目要涉及到不同的数据库,更多的是为了以后换库方便。在代码里直接写SQL语句进行查询。发生换库时,只要简单的换一下连接字符串就可以了。这叫以不变应万变,怎么讲呢,代码不变,数据库爱怎么变就怎么变换库可以灵活到什么程度,即使公司打算把Oracle项目换成Access做单机版也没有任何问题。
能:存储过程的执行速度要快得多,一方面它不需要在业务服务器和数据库服务器之间折返,更因为它有一次编译,较投递 SQL语句而言,效率更高。况且Oracle可以用Java,C 编写更高效的存储过程,SQL Server 2005则可以使用 .net,毫无疑问,存储过程的速度比美名其曰的业务组件要快的多。
一般人喜欢折衷。行嘛,既然二者都有理,我在适当的场合选择适当的技术,需要效率高的时候,我才用存储过程。
我喜欢一边倒。既然存储过程效率高,为什么不用存储过程?
为了实现一个很小的业务运算,需要把数据提过来,然后又投回去,需要把数据库的数据类型转换成程序语言的数据类型,做完这些无谓的工作之后,最后的效果却是抛回到数据库,只要想到这一点,我们为什么不使用存储过程而采用这种低效的方案。
须知现在的问题背景和那个数据库山头林立的年代已经完全不同了。假如说数据库也存在阶段的话,那种能否支持存储过程都参差不齐的年代属于石器时代。目前的数据库,连mysql都要支援存储过程了,也就是说,它们基本上都在同一条起跑线。因此,无须考虑万一我写了存储过程,而“升迁”的目标数据库不支援存储过程的问题。
但是,各种数据库的存储过程的编写方法并不一致。假如从SQLServer转移到Oracle,意味着几乎所有的存储过程都要重写。这是换库说在新时代的新说辞。这个说法是有事实凭据的,换库多麻烦,存储过程要全部重写。
实际上这个问题很轻易解决——代码生成。做好一个存储过程的模板,将模板中的数据库类型标识换成"Oracle",重新生成一次便万事大吉了。既然不需要手工重写,这个说法自然也站不住脚。或者制做一种调和于主流数据库存储过程语言的中间语言也可以,一个简单的编译器将它转换为特定于某个数据库的语言。
使用存储过程除了效率高之外还有其他优势:
a) 业务规则脚本化。编写存储过程的SQL语句是一种业务规则的脚本,当业务规则发生变化时,无须对业务组件使用编译和替换的高超魔法——须知越高超越危险。存储过程将这个问题简单的化解。
b) 可以直接应用数据库内置的权限治理。假如不使用存储过程,一个业务规则能不能运行,需要在业务组件里进行控制,因此业务组件中需要另起一套权限控制框架,这个框架和数据库本身的权限控制是同构的,正所谓叠床架屋,多此一举。而利用数据库的权限治理则从源头把关,直接安全。
c) 语言纯粹。和在 .net/java代码中混杂数据库操控的笨拙代码相比,存储过程完全使用SQL语言,尽管各种数据库的方言有异。提取数据,处理数据,保存结果的工作用同一种语言融在一处,无疑属于更好的编程风格。这个问题可以类比于 ASP-Asp.Net,从代码混杂到各自纯粹。.net3.0 意图使用 DLink技术把 SQL 语言变成编程语言的一部分,想法是类似的。
但相较于各自纯粹而言,那种在 VB里直接写 FROM SELECT WHERE的做法恐怕也不可取。虽然我完全支持 Class Employee 这种语言级别的 ORM。
d) 便于移植。和以前数据库山头林立的年代不同,现在我们更不放心的是平台。假如我们使用 .net 平台,意味着我们放弃了传说中更坚强的Linux —— mono 恐怕还靠不住。假如我们使用 java,虽然得到了跨平台的好处,但是只能做 bs,而在 Windows平台又失去了 .net 所具备的高速度——后者是致命的。另外,假如客户需要一个对配置要求不高反应又不迟钝的 cs 程序,也许用 Delphi 更能符合用户的愿望。
我们通常要在 win32/.net/java 平台之间做出选择。而这种选择没有回头路可走。假如我们把数据库操作代码放在数据访问层,把业务规则代码放在业务层,一切都特定于某种平台了。相反,假如使用存储过程,业务规则部署于数据库中,数据访问层和业务层都只剩下一个壳,代码大为简化,使用任何平台的语言来改写都可以快速完工。想想看,一周的时间就可以在一个新平台写完前端,何乐而不为呢。
e) 方便的使用数据库的事务机制。在程序语言中控制事务,无论哪个平台都有隔靴搔痒之感,而在纯粹的SQL语言则显得心应手,浑然一体。
就我理解,像便于换库这种理由,从历史原因来考察,是软件开发伤口上的痛。这个问题一直到ODBC等等方案风行之后才得到解除。以前特定于某种类型的数据库的程序,升迁所花费的代价是非常昂贵的。这次大动荡给一代人投下了心理阴影,提到特定于某种数据库时,便产生一朝被蛇咬,十年怕井绳的惧怕心理。
类似的事情还有函数嵌套函数。结构化程序设计中,一个要害性的观念就是功能独立,其程序功能被划分为多个独立的功能函数。但有些功能是受雇于另外的子功能的,并非第一级子功能。例如,通常一个递归会写成两块,一块发起递归,一块递归,在类似无嵌套函数的语言中,发起递归会变成一个函数,递归过程又作为一个函数,这两个函数地位平行。而在支持嵌套函数的语言中,递归过程可以实现为一个子函数,嵌入进发起函数。但是现在的语言多不支持嵌套,因为OO浪潮使人们畏惧结构化,即使本不相左的交集。唯最近微软披露的 .net 3.0 框架计划引入该机制,也许是Anders很怀念Pascal,当然,更大的可能是微软也意识到了这个问题。