前一阵子看到网上关于是否该使用存储过程的一些争论,对我颇有启发,当初我们在做系统设计时,对是否该使用存储过程,也争论了很久,最后还是没有定论,留了一些存储过程,但是业务逻辑都在代码里,现在想来是一个明智的决定。这个争论使我想起一个架构设计时的词语“粘合层”,下面我把自己这方面的心得和感受详细的描述一下。
一般情况下,我做设计时,很有可能会使用上图这种结构。粘合层使用什么方式完全是随客观情况来定的。(粘合层一般有把不一致的东西粘合在一个系统中这个作用,用流行的话来说,OO和关系数据库不匹配,有阻抗,粘合层就是想办法让两个不大对脾气的人协力完成系统的目标)
业务逻辑层控制产品的业务逻辑,而持久化层提供业务对象的持久化功能。但是持久化层,可能也并不清楚对象和数据库表结构之间的关系。而是要粘合层来处理。
对持久化层来讲,我只知道把数据存储到数据库中了,至于对象的几个属性是否拼成一个字段来保存了,还是一个字段对应一个属性,甚至一个属性劈开成几个字段,都和业务逻辑层、持久化层无关。他们只关心,从外部来看,这个对象有哪些属性就可以。
这么来看,粘合层有几种选择,首选(也是最流行的)就是使用ORM机制,例如Hibernate;第二,自己做一个粘合层,和第一种做法基本一致;第三,使用数据库的机制(例如存储过程、触发器、数据库自定义函数)来进行粘合。
我们把(1)和(2)看成一种思路,到底使用程序中使用粘合层和使用数据库粘合层,哪种更好呢?就像很多人说的,没有最好,只有合适不合适。
说一个简单的粘合层,比如我的产品要支持多种数据库,那么,我的代码中,如果所有的查询都是直接从表查询的,在跨数据库时,就会面临一个问题,比如有些内容,在Oracle中可以设计成一个表,但是在SQLServer中需要设计成两个表才行,那么,移植部分就会非常痛苦。但是如果我的查询都是对应视图的,管你几个表,只要某个视图能向程序提供需要的数据就行,那么Oracle里一个表,SQLServer的两个表,设计上就自由多了。这样,这些视图就可以称之为“粘合层”,避免了“表”和代码的硬挂接,留下了一些设计上扩展和变更的余地。
代码中的粘合层,例如使用ORM,我就不多写了,ITEye的很多高手比我精通。
1.在生产环境下,可以通过直接修改存储过程的方式修改业务逻辑(或bug),而不用重启服务器。但这一点便利被许多人滥用了。有人直接就在正式服务器上修改存储过程,而没有经过完整的测试,后果非常严重。
2.执行速度快。存储过程经过编译之后会比单独一条一条执行要快。但这个效率真是没太大影响。如果是要做大数据量的导入、同步,我们可以用其它手段。
3.减少网络传输。存储过程直接就在数据库服务器上跑,所有的数据访问都在服务器内部进行,不需要传输数据到其它终端。但我们的应付服务器通常与数据库是在同一内网,大数据的访问的瓶颈会是硬盘的速度,而不是网速。
4.能够解决presentation与数据之间的差异,说得文艺青年点就是解决OO模型与二维数据持久化之间的阻抗。 领域模型和数据模型的设计可能不是同一个人(一个是SA,另一个是DBA),两者的分歧可能会很大——这不奇怪,一个是以OO的思想来设计,一个是结构化 的数据来设计,大家互不妥协——你说为了软件的弹性必须这么设计,他说为了效率必须那样设计,为了抹平鸿沟,就用存储过程来做数据存储的逻辑映射(把属性 映射到字段)。好吧,台下已经有同学在叨咕ORM了。
5.方便DBA优化。所有的SQL集中在一个地方,DBA会很高兴。这一点算是ORM的软肋。不过按照CQRS框架的思想,查询是用存储过程还是ORM,还真不是问题——DBA对数据库的优化,ORM一样会受益。况且放在ORM中还能用二级缓存,有些时候效率还会更高。
弊端:
1.SQL本身是一种结构化查询语言,加上了一些控制(赋值、循环和异常处理等),但不是OO的,本质上还是过程化的,面对复杂的业务逻辑,过程化的处理会很吃力。这一点算致命伤。
2.不便于调试。基本上没有较好的调试器,很多时候是用print来调试,但用这种方法调试长达数百行的存储过程简直是噩梦。好吧,这一点不算啥,C#/java一样能写出噩梦般的代码。
3.没办法应用缓存。虽然有全局临时表之类的方法可以做缓存,但同样加重了数据库的负担。如果缓存并发严重,经常要加锁,那效率实在堪忧。
4.无法适应数据库的切割(水平或垂直切割)。数据库切割之后,存储过程并不清楚数据存储在哪个数据库中。
5.精通SQL的新手越来越少——不要笑,这是真的,我面试过N多新人,都不知道如何创建全局临时表、不知道 having、不知道聚集索引和非聚集索引,更别提游标和提交叉表查询了。好吧,这个缺点算是凑数用的,作为屌丝程序员,我们的口号是:没有不会的,只有 不用的。除了少数有语言洁癖的人,我相信精通SQL只是时间问题。
在实际的过程中,我的做法是:
(1)坚决不能把业务过程写在数据库中
(2)在设计过程中,因为大多数时候,数据存取效率必须考虑,所以会使用一些数据库机制来做粘合层,这部分,都安排给组内精通数据库的工程师(例如之前我们产品组有一个DBA,程序方面一般但是能按要求写代码,数据库方面功力很深)来做。
(3)数据库的粘合层,是为了解决数据形式转换的问题
(4)持久层通过粘合层取出的数据,和业务对象是一致的;但实际数据库表中的数据,可能形态、结构和业务对象差的很远,例如一个对象可能分存在两个表中;也可能两个对象存在一个表中(这是粘合层存在的意义)
(5)跨数据库的工作可以由粘合层完成,但是也可以在代码里完成。如果组内没有精通数据库的,代码是优先选择。