链接:/a/38993
之所以起这个标题,不是为了吸引眼球,也不是为了想在这篇文章里对存储过程进行教科书式的讨论。阿里巴巴 Java 开发手册中规定:
结合我最近在项目中遇到的存储过程问题,今天打算讲一下这个问题。
这个故事开始于去年我去武汉出差时一位同事的一个问题。问题是:
★
我认为存储过程非常有用,为什么不建议使用它们?
”
当时我好像有很多话要说,但是没有用实际例子去回答同事,只是讲了代码比SQL更可重用,可扩展,通用性强这个结论,我猜同事还是不服气的。
现在想想,我最近遇到的这个问题,也可以当做一个例子来回答一下同事们。
最近项目有一个新的需求,就是验证用户是否有Job,Certification,Disclosure三项业务数据。
查看代码后,我发现系统用户个人页面的 C# 代码调用了三个存储过程来捕获用户的 Job、Certification 和 Disclosure 数据。
我的新要求是重用这三个存储过程,否则:
★
如果把爬取数据的业务逻辑代码都写在各个地方,一旦业务逻辑发生变化,读取Job、Certification、Disclosure的所有SQL将很难追溯和维护。
”
如果我从 C# 代码中调用三个现有的存储过程,事情就会很快结束,这就是我所做的。
但是代码审查人员认为我的需求不需要Job、Certification、Disclosure这三个业务对象的数据,只需要知道给定用户是否有Job、Certification或者Disclosure就行,所以我应该把是否有Job、Certification或者Disclosure的判断逻辑写在数据库中,最后只通过网络从数据库传输true或者false,节省网络流量,这样最好。
对的。抛开网络性能不说,从接口设计的角度来说,接口的输入和返回值应该是你需要的数据,不应该携带大量不需要或者需要调用者预处理的数据。从接口的语义表达就能知道调用的目的,这样会大大提高代码的可读性。
然后我就改了。但是没想到问题来了。
为了说明问题,我简化代码,假设系统中现有的存储过程如下:
CREATEPROCEDURE[dbo].[获取作业]
@PersonIdint,
@OrganizaitionIdint
作为
开始
选择 JobId、JobName、JobTypeFROMJobWHERE PersonId = @PersonId AND OrganizationId = @OrganizationId
结尾
我在一个新的存储过程中调用它,我需要获取该人的工作数量,即 GetJobs 返回的结果集的数量。
为了达到这个目的,首先想到的就是使用临时表,将返回的结果集存放到临时表中,然后对其执行count(*)操作:
创建过程[dbo].[MyProc]
@PersonIdint,
@OrganizaitionIdint,
作为
开始
创建表#Temp(
PersonIdint,
组织标识
插入#Temp EXEC dbo.GetJobs
@PersonId = @PersonId,
@ParentOrgId = @ParentOrgId
从 #Temp 选择 COUNT(*)
结尾
这种方式简单有效,但是维护问题比较严重,如果以后调用的存储过程返回结果集的字段发生变化,MyProc中的临时表结构也需要随之改变,这是不可接受的。
如果我们用 SELECT INTO 替换 MyProc 中的 INSERT INTO 语句会怎么样?不幸的是,答案是否定的。SQL 本身不支持这种用法。
为现有的存储过程 GetJobs 添加输出参数?在这种情况下,由于 GetJobs 已被许多其他代码或 SQL 脚本调用,因此修改现有的存储过程会有风险。
我在网上搜了一下,找到了一篇MS MVP写的文章,总结了存储过程间传输数据的几乎所有方法:如何在存储过程间共享数据。他在文中也无奈的说:
★
请记住,与 C# 和 Java 等语言相比,Transact-SQL 的代码重用能力较差,因此 T‑SQL 中重用代码的解决方案更加笨拙。
”
最终没能找到满意的解决办法,只好在新写的存储过程中重写查询Jobs的语句。
存储过程在很多场景下都有优势,比如性能。但是不建议把业务逻辑的公用方法写在存储过程中,代码复用和扩展性远不如客户端语言,或许可以,但是成本和风险比客户端语言要高,得不偿失。
天知道我是否还有机会与那位前同事再次讨论这个话题。
——End—— 后台回复关键字:1024,获取一份精心整理的技术干货 后台回复关键字:进群,带你进入高手如云的交流群。 推荐阅读