作为主要编写c#的开发人员,我在编写c#代码时采用了一些好的做法。当我有时编写存储过程时,我很难将这些实践应用于存储过程代码。
有几次我继承了噩梦存储过程代码,前三层或四层存储过程设置了一些临时表,并且主要是相互调用。没有真正的工作,只有几行代码。然后最后调用“最终”存储过程,一个3000-5000行SQL代码的大怪物。这段代码通常有很多代码味道,比如代码重复,错综复杂的控制流程(又名意大利面条),以及一种方法,它可以将太多的东西堆叠在一起,没有明确的分离,其中一块工作开始,它的结束(甚至不是作为除数评论。)
我还注意到使用了从中间临时表中选择的注释选择语句。可以重新打开选择以进行调试,但需要在任何调用代码期望返回结果集的特定顺序之前将其删除。
显然,我的队友们也分享了我缺乏良好的SQL写作习惯。
所以......(这是真正的问题)...编写模块化可维护存储过程的好方法是什么?
欢迎自制的做法和对书籍/博客的引用。方法以及帮助完成某些任务的工具。
让我们总结一些我没有找到良好实践的领域
大多数情况下,我认为SQL Server是DBMS,但DBMS不可知的答案或答案指出了其他DBMS的功能:在上述情况下也有帮助。
为了给出一些背景知识:我遇到的大多数大型存储过程都在报告场景中,其基础是从大型表中创建一些汇总值。但是一路上你需要排除一些异常表中的一些值,在一些尚未完成的东西表中添加一些值,与去年相比(你能想象处理产品变更部门的丑陋代码)年间?)等。
答案 0 :(得分:14)
我写了很多复杂的存储过程。我会考虑最好的做法:
不要在存储过程中使用动态SQl,除非您正在进行搜索过程,其中包含许多可能需要或可能不需要的参数(那么它是目前最好的解决方案之一)。如果必须在proc中使用动态SQl,则始终具有调试输入参数,并且如果设置了debug参数,则打印创建的SQL语句而不是执行它。这将节省数小时的调试时间!
如果您在proc(插入/更新/删除)中执行多个操作查询,请使用Try Cacth块和事务处理。将一个测试参数添加到输入参数中,当它设置为1时,始终回滚整个事务。在回滚测试模式之前,我通常会有一个部分返回我正在影响的表中的值,以确保我认为我对数据库所做的事实上是我所做的。或者您可以按照以下所示进行检查。就像使用@test参数一样,只需在当前注释掉的选项周围输入以下代码(并取消注释它们)即可。
If @test =1
Begin
Select * from table1 where field1 = @myfirstparameter
End
现在,您无需每次测试时都通过评论和取消注释。
@test或@debuig应始终设置为默认值0并放在列表的最后。这样添加它们就不会破坏proc的现有调用。
考虑为proc进行插入/更新/删除的日志记录和/或错误记录表。如果在执行时记录表变量中的步骤和/或错误,则在将回滚插入日志记录表后,它们仍然可用。知道复杂过程的哪个部分失败以及错误是什么,以后会非常宝贵。
尽可能不嵌套存储过程。如果需要在循环中运行多个记录,请将存储的proc替换为具有表值参数的proc,并将proc设置为以基于集合而非单独的记录方式运行。如果表值参数有一条记录或多条记录,这将有效。
如果您有一个包含大量子查询或派生表的复杂选择,请考虑使用CTE。重构任何相关子查询或游标以更好地执行基于集合的代码。总是在数据集方面思考而不是一条记录。
在任何可以想象的情况下,不要筑巢。性能损失远远低于任何少量节省的开发时间。并且相信我,嵌套视图不会节省维护时间,因为更改需要最远到视图链中的视图。
所有存储过程(和其他数据库代码)都应该在源代码管理中。
表变量适用于较小的数据集,但临时表(以#或##而非临时表开头的真实表)可以更好地处理大型数据集中的性能。如果使用临时表,则在不再需要时删除它们。尽量避免使用全局临时表。
学习编写高性能SQL。编写SQL的效果通常与SQL一样容易,只有在您了解技术后才能执行。如果你编写复杂的存储过程,没有理由不知道哪种技术比其他技术更好。了解如何确保您的查询是可以查询的。避免使用游标,相关子查询,标量函数以及其他逐行排列的事物。
答案 1 :(得分:5)
通过临时表进行通信有时会产生巨大的代码味道。这些程序通常不能由用户运行而不会相互干扰(如果您为不同的程序重复使用临时表名称,并且不会重新创建它们,或者如果您使用相同的名称和两个不同的表格模式)。它们可能很难排除故障 - 就像任何功能一样,必要时使用它们并且不存在更好的替代方案。临时使用真实表也可能有问题。
在SQL Server中完全相互传递数据的存储过程(超过参数)可能会有问题。现在有表值参数,现在可以使用内联表值函数或(并且通常更喜欢)多语句表值函数来完成以前用proc进行的许多事情。
在SQL Server中,避免在大型行集上大量使用标量函数和多语句表值函数 - 它们表现不佳,因此在C#中看起来很明显的模块化技术在这里并不适用。
我建议你看一下2002年出版的Ken Henderson's Guru's Guide to SQL Server Stored Procedures,它仍然有很多关于数据库应用程序设计的有用信息。
答案 2 :(得分:2)
这是一个很好的问题。作为一个C#dev自己不得不涉足SQL,似乎SQL本质上妨碍了我习惯使用C#的最佳实践。
Common Table Expresions非常适合隔离存储过程中的查询,但您只能使用它们一次!这导致您定义视图,但之后您已经失去了封装。
一个存储过程的结果集很难在另一个存储过程中使用,因此您可能想要编写表值函数。这会增加您的权限维护负担并迫使您将函数“两次”写入 - 一次作为函数,另一次作为调用函数的过程。否则,您的DAL具有不同的接口,具体取决于它是否是一个过程。
随着时间的推移,所有这些都导致我在数据库中坚持使用简单的CRUD存储过程(不会相互调用),并且当关系复杂时,很少会查看隔离的查询。更多BI的东西。其他一切都在BLL中。
从物理上讲,SQL在单独的文件中被按功能或它们围绕源表控制的表格进行隔离。
避免使用SELECT *并支持指定列。当您更改表并且不触及所有过程时,这可以避免运行时问题。是的,有一个procs重新编译,但它会遗漏一些,特别是如果涉及到视图。此外,SELECT *几乎总是返回比实际需要更多的列,这是浪费带宽。
答案 3 :(得分:1)
在SQL代码编写方面,以上注释对Do和Dont的建议非常有用。如果我正确理解了您的问题,那么您正在问这对于SQL Developer在单个存储过程中编写数百甚至数千个代码是否正常?在C#中,这是一个很大的禁忌。您将使用方法,程序集和类将逻辑封装到小型卡盘中。 SQL Developer倾向于将整个逻辑写在一个存储过程中,以完成相关任务。如上面的HLGEM所述,“如果可能,请勿嵌套存储过程”。不要嵌套视图。
例如:一个简单的C#获取和插入设计如下:
SQL开发人员将像这样设计它: 在一个存储的过程中:
如果要更改SQL的编写方式以匹配C#Developer的编写结构,则可以执行以下操作: