编写sql存储过程的最佳实践是什么?

时间:2008-11-19 21:25:21

标签: sql-server tsql stored-procedures

我发现SQL存储过程非常有趣且有用。我已经编写了存储过程,但我想为任何类型的要求编写精心设计,性能良好的调优和简洁的SP,并且也希望了解存储过程的任何技巧或良好实践。在编写存储过程时如何从初学者转到高级阶段?

更新:从评论中发现我的问题应该更具体。 每个人都有一些技巧,我期待他们在他们的代码中使用SP的技巧和做法,使他们与其他人区别开来,更重要的是提高写作和使用存储过程的工作效率。

11 个答案:

答案 0 :(得分:40)

以下是我的存储过程错误处理指南。

  • 使用其完全限定名称调用每个存储过程以提高性能:这是服务器名称,数据库名称,架构(所有者)名称和过程名称。
  • 在创建每个存储过程的脚本中,明确指定允许执行过程的角色,例如public或者其他。
  • 使用sysmessage,sp_addmessage和占位符而不是硬编码的错误消息。
  • 使用sp_addmessage和sysmessages时,请始终使用错误消息编号50001或更高。
  • 使用RAISERROR时,始终为警告消息提供严重级别< = 10。
  • 使用RAISERROR时,始终为错误消息提供11到16之间的严重性级别。
  • 请记住,即使在触发器环境中,使用RAISERROR并不总是中止正在进行的任何批处理。
  • 在使用本地变量或查询之前将@@error保存到本地变量。
  • 在使用或查询之前将@@ rowcount保存到局部变量。
  • 对于存储过程,请使用返回值仅表示成功/失败,而不是任何其他/额外信息。
  • 存储过程的返回值应设置为0表示成功,非零表示失败。
  • 将ANSI_WARNINGS设置为ON - 这将检测任何聚合分配中的空值,以及超出字符或二进制列的最大长度的任何分配。
  • 设置NOCOUNT ON,原因有很多。
  • 仔细考虑是否需要XACT_ABORT ON or OFF。无论你走哪条路,都要保持一致。
  • 退出第一个错误 - 这实现了KISS模型。
  • 执行存储过程时,请始终检查@@ error和返回值。例如:

    EXEC @err = AnyStoredProc @value
    SET  @save_error = @@error
    -- NULLIF says that if @err is 0, this is the same as null
    -- COALESCE returns the first non-null value in its arguments
    SELECT @err = COALESCE( NULLIF(@err, 0), @save_error )
    IF @err <> 0 BEGIN 
        -- Because stored proc may have started a tran it didn't commit
        ROLLBACK TRANSACTION 
        RETURN @err 
    END
    
  • 执行导致错误的本地存储过程时,请执行回滚,因为该过程可能已启动它未提交或回滚的事务。
  • 不要以为只是因为你没有开始交易,没有任何活跃的交易 - 来电者可能已经开始了。
  • 理想情况下,避免在调用者启动的事务上进行回滚 - 请检查@@ trancount。
  • 但是在触发器中,总是进行回滚,因为您不知道调用者是否发起了活动事务(因为@@ trancount总是&gt; = 1)。
  • 在以下陈述后始终存储并检查@@ error:

    INSERT, DELETE, UPDATE
    SELECT INTO
    Invocation of stored procedures
    invocation of dynamic SQL
    COMMIT TRANSACTION
    DECLARE and OPEN CURSOR
    FETCH from cursor
    WRITETEXT and UPDATETEXT
    
  • 如果DECLARE CURSOR在进程全局游标(默认值)上失败,请发出一个语句来释放游标。
  • 小心UDF中的错误。当UDF中发生错误时,函数的执行会立即中止,调用UDF的查询也会中止 - 但@@错误为0!在这些情况下,您可能希望使用SET XACT_ABORT ON运行。
  • 如果要使用动态SQL,请尝试在每个批处理中只有一个SELECT,因为@@ error仅保存最后执行的命令的状态。一批动态SQL中最可能出现的错误是语法错误,SET XACT_ABORT ON不会处理这些错误。

答案 1 :(得分:17)

我总是尝试使用的唯一技巧是:始终在顶部附近的注释中包含示例用法。这对于测试SP也很有用。我喜欢包含最常见的示例 - 然后您甚至不需要SQL Prompt或单独的.sql文件与您最喜欢的调用,因为它存储在服务器中(如果您有存储的procs,那么它特别有用) sp_who输出块或其他什么,并采取一堆参数)。

类似的东西:

/*
    Usage:
    EXEC usp_ThisProc @Param1 = 1, @Param2 = 2
*/

然后,要测试或运行SP,只需在脚本中突出显示该部分并执行。

答案 2 :(得分:11)

这是一个非常普遍的问题,但这里有几条建议:

  • 一致地命名存储过程。许多人使用前缀来识别它是一个存储过程,但不要使用'sp_'作为为主数据库指定的前缀(无论如何在SQL Server中)
  • 设置NOCOUNT,因为这会减少可能的返回值
  • 基于集合的查询通常比游标执行得更好。 This question更详细地介绍了这一点。
  • 如果您正在为存储过程声明变量,请使用良好的命名约定,就像在任何其他类型的编程中一样。
  • 使用其完全限定名称调用SP,以消除有关应调用哪个SP的混淆,并帮助提高SQL Server性能;这样可以更容易地找到有问题的SP。

当然还有更多。这是一个更多的链接:   SQL Server Stored Procedures Optimization Tips

答案 3 :(得分:11)

  1. 始终使用SET NOCOUNT ON
  2. 如果您要执行两次或更多次插入/更新/删除,请使用交易。
  3. 永远不要命名你的过程'sp_'。 SQL Server将首先查看master数据库,而不是找到它,然后再查看数据库。如果以不同的方式命名proc,则SQL Server将首先查看您的数据库。
  4. 为:

    SET NOCOUNT ON
    BEGIN TRAN
      INSERT...
      UPDATE...
    COMMIT
    

    更好,但看起来很乱并且编码很麻烦:

    SET NOCOUNT ON
    BEGIN TRAN
      INSERT...
      IF @ErrorVar <> 0
      BEGIN
          RAISERROR(N'Message', 16, 1)
          GOTO QuitWithRollback
      END
    
      UPDATE...
      IF @ErrorVar <> 0
      BEGIN
          RAISERROR(N'Message', 16, 1)
          GOTO QuitWithRollback
      END
    
      EXECUTE @ReturnCode = some_proc @some_param = 123
      IF (@@ERROR <> 0 OR @ReturnCode <> 0)
           GOTO QuitWithRollback 
    COMMIT
    GOTO   EndSave              
    QuitWithRollback:
        IF (@@TRANCOUNT > 0)
            ROLLBACK TRANSACTION 
    EndSave:
    

    好:

    SET NOCOUNT ON
    SET XACT_ABORT ON
    BEGIN TRY
        BEGIN TRAN
        INSERT...
        UPDATE...
        COMMIT
    END TRY
    BEGIN CATCH
        IF (XACT_STATE()) <> 0
            ROLLBACK
    END CATCH
    

    最佳:

    SET NOCOUNT ON
    SET XACT_ABORT ON
    BEGIN TRAN
        INSERT...
        UPDATE...
    COMMIT
    

    那么“最佳”解决方案的错误处理在哪里?你不需要任何。请参阅 SET XACT_ABORT ON ,这意味着如果有任何错误,请执行自动回滚。代码更清晰,更易于阅读,更易于编写,并且减少了错误。因为SQL Server现在为您执行此操作,因此不会错过任何错误,因为没有错误。

答案 4 :(得分:3)

在SQL Server中,我总是放置一个语句,如果它存在,将删除该过程,因此我可以轻松地在开发过程中重新创建过程。类似的东西:

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'usp') AND type in (N'P', N'PC'))
DROP PROCEDURE usp

答案 5 :(得分:2)

这在很大程度上取决于您在存储过程中所做的事情。但是,如果在一个proc中进行多次插入/更新或删除,则最好使用事务。这样,如果一个部分失败,其他部分将回滚,使数据库保持一致状态。

写入数据库时​​要考虑的两个最重要的事情(因此当使用执行除select之外的操作的存储过程时)是数据完整性和性能。如果没有数据完整性,您只需拥有一个包含垃圾的数据库,并且无用。如果没有performacne,您将没有用户(如果他们在客户之外)或不满意的用户(如果他们被要求使用您的产品,通常是内部用户,他们别无选择去其他地方)。这对你的职业生涯都没有好处。因此,在编写存储过程时,请确保首先确保将数据正确输入数据库,并且如果操作的某个部分出现问题,则数据将失败。

如果需要在proc中写入检查以确保您的最终结果是正确的。我是ETL专家,在尝试将数据导入表格之前,我总是编写我的过程以使数据清理和规范化。如果你是从用户界面做的事情,这可能不是那么重要做在矿井PROC,虽然我会在用户inteface做检查,即使运行PROC,以确保数据之前是良好的插入(之类的检查,以确保日期字段包含实际日期,所有必填字段都有值等。)

如果您正在编写将大量数据放入表中的过程,那么最好有一种方法在最终确定之前测试这些结果。您会对客户和供应商提供的数据导入垃圾感到惊讶。我们使用测试标志编写所有导入过程。这样您就可以返回选择数据而不是执行操作,这样您就可以提前看到您将要影响的内容。

我不是动态SQL的粉丝,我不想在存储过程中使用它。如果您在现有的proc中遇到动态SQl,请输入一个调试标志,允许您打印SQL而不是执行它。然后在评论中添加您需要运行的最典型案例。如果你这样做,你会发现你可以更好地维护proc。

不要在游标中执行操作,因为您希望重用另一个仅在一条记录上运行的存储过程。如果是坏事,代码重用会导致性能问题。

如果您正在使用case语句或if语句,请确保已完成将触及每个可能分支的测试。你没有测试过的那个是失败的那个。

答案 6 :(得分:1)

这不是一个可以在没有更多信息的情况下直接回答的问题,但是一些一般的经验法则确实适用。

存储过程只是存储的T-SQL查询。因此,熟悉T-SQL以及各种函数和语法确实是您需要做的。从性能角度来看,您需要确保您的查询和底层数据结构以允许良好性能的方式匹配。 IE,确保在需要的地方实现索引,关系,约束等。

了解如何使用性能调优工具,了解执行计划的工作方式,以及具有这种性质的事情是如何实现“下一级”

答案 7 :(得分:1)

使用SQL Server 2008使用TRY ... CATCH构造,您可以在T-SQL存储过程中使用该构造,通过检查@@ ERROR,为SQL Server的早期版本提供更优雅的异常处理机制(并且经常使用GOTO语句)在每个SQL语句之后。

         BEGIN TRY
             one_or_more_sql_statements
         END TRY
         BEGIN CATCH
             one_or_more_sql_statements
         END CATCH

在CATCH块中,您可以使用以下错误函数来捕获有关调用CATCH块的错误的信息,

         ERROR_NUMBER()
         ERROR_MESSAGE()
         ERROR_SEVERITY()
         ERROR_STATE()
         ERROR_LINE()
         ERROR_PROCEDURE()

与由执行的每个语句重置的@@ error不同,错误函数检索的错误信息在TRY ... CATCH语句的CATCH块范围内的任何位置保持不变。这些函数可以将错误处理模块化为单个过程,因此您不必在每个CATCH块中重复错误处理代码。

答案 8 :(得分:0)

基本的东西:

制定错误处理策略,并在所有SQL语句中捕获错误 确定对存储过程使用源代码控制的策略 包含一个带有用户,日期/时间和sp目的的注释标题 成功执行时显式返回0(成功),否则返回其他内容 对于非平凡程序,包括测试用例(或案例)和预期结果的描述 养成性能测试的习惯。对于文本案例,至少记录执行时间 理解显式交易,并使用它们 几乎从不从SP调用SP。可重用性是与SQL不同的球赛。

答案 9 :(得分:0)

以下是一些最佳做法,

  1. 避免_sp前缀
  2. 包括SET NOCOUNT ON语句
  3. 尝试避免使用临时表
  4. 尝试避免使用Select * from
  5. 尝试避免使用游标
  6. 使用正确的索引编制
  7. 正确的错误处理

For more explanations and T-SQL code samples please check this post

答案 10 :(得分:0)

这里有一些代码可以证明SQL Server上没有多级ROLLBACK,并且它说明了如何处理事务:


BEGIN TRAN;

    SELECT @@TRANCOUNT AS after_1_begin;

BEGIN TRAN;

    SELECT @@TRANCOUNT AS after_2_begin;

COMMIT TRAN;

    SELECT @@TRANCOUNT AS after_1_commit;

BEGIN TRANSACTION;

    SELECT @@TRANCOUNT AS after_3_begin;

ROLLBACK TRAN;

    SELECT @@TRANCOUNT AS after_rollback;