构建存储过程的最佳实践

时间:2011-08-24 19:21:51

标签: sql stored-procedures

作为主要编写c#的开发人员,我在编写c#代码时采用了一些好的做法。当我有时编写存储过程时,我很难将这些实践应用于存储过程代码。

有几次我继承了噩梦存储过程代码,前三层或四层存储过程设置了一些临时表,并且主要是相互调用。没有真正的工作,只有几行代码。然后最后调用“最终”存储过程,一个3000-5000行SQL代码的大怪物。这段代码通常有很多代码味道,比如代码重复,错综复杂的控制流程(又名意大利面条),以及一种方法,它可以将太多的东西堆叠在一起,没有明确的分离,其中一块工作开始,它的结束(甚至不是作为除数评论。)

我还注意到使用了从中间临时表中选择的注释选择语句。可以重新打开选择以进行调试,但需要在任何调用代码期望返回结果集的特定顺序之前将其删除。

显然,我的队友们也分享了我缺乏良好的SQL写作习惯。

所以......(这是真正的问题)...编写模块化可维护存储过程的好方法是什么?

欢迎自制的做法和对书籍/博客的引用。方法以及帮助完成某些任务的工具。

让我们总结一些我没有找到良好实践的领域

  • 模块化和封装(存储过程通过临时表进行通信真的要走了吗?)
    • 在c#中,我使用用访问修饰符修饰的程序集,类和方法来完成此任务。
  • 调试/测试(比修改调试目标更好?)
    • 调试工具?
    • 调试痕迹?
    • 测试夹具?
  • 使用代码结构代码强调代码/逻辑/数据/控制流程
    • 在c#中,我重构并分解出较小的方法,每个方法只执行一个逻辑任务。
  • 代码重复

大多数情况下,我认为SQL Server是DBMS,但DBMS不可知的答案或答案指出了其他DBMS的功能:在上述情况下也有帮助。

为了给出一些背景知识:我遇到的大多数大型存储过程都在报告场景中,其基础是从大型表中创建一些汇总值。但是一路上你需要排除一些异常表中的一些值,在一些尚未完成的东西表中添加一些值,与去年相比(你能想象处理产品变更部门的丑陋代码)年间?)等。

4 个答案:

答案 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#获取和插入设计如下:

  • 调用GetData方法
  • 调用获取数据方法
  • 调用转换数据方法
    • 调用CheckAlphaNumeric方法
    • 通话数据充实方法
  • 呼叫负载转换数据方法

SQL开发人员将像这样设计它: 在一个存储的过程中:

  • 使用临时表或表变量获取数据并进行转换,然后将其加载到最终表中。

如果要更改SQL的编写方式以匹配C#Developer的编写结构,则可以执行以下操作:

  • 执行主存储过程(调用以下过程)。
    • 执行GetData存储过程并加载到阶段表中
    • 执行读取阶段表并转换数据的转换存储过程
    • 执行“加载数据”存储过程以将Stag ed 或转换后的数据加载到最终表中。