【问题标题】:What are the best practices in writing a sql stored procedure [closed]编写sql存储过程的最佳实践是什么[关闭]
【发布时间】:2008-11-19 21:25:21
【问题描述】:

我发现 SQL 存储过程非常有趣和有用。我已经编写了存储过程,但我想为任何类型的需求编写精心设计、良好性能调整和简洁的 SP,并且也很想了解存储过程的任何技巧或良好实践。编写存储过程如何从初级进入高级阶段?

更新:从 cmets 发现我的问题应该更具体。 每个人都有一些技巧,我期待他们在代码中使用这些技巧和实践,以使他们与其他人区分开来,更重要的是提高编写和使用存储过程的生产力。

【问题讨论】:

  • 这类似于说,“我写过故事,但我想为所有类型的读者写精心制作的畅销书和小说,也很想了解任何写作技巧或良好做法故事。”可能想要更具体地了解您要查找的内容...
  • 同意,更具体的问题会更好。

标签: sql-server tsql stored-procedures


【解决方案1】:

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

  • 使用其完全限定名称调用每个存储过程以提高性能:即服务器名称、数据库名称、架构(所有者)名称和过程名称。
  • 在创建每个存储过程的脚本中,明确指定允许哪些角色执行该过程,例如 public 或其他。
  • 使用 sysmessage、sp_addmessage 和占位符,而不是硬编码的错误消息。
  • 使用 sp_addmessage 和 sysmessages 时,始终使用 50001 或更大的错误消息号。
  • 对于 RAISERROR,始终为警告消息提供严重级别
  • 对于 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 始终 >= 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 的查询也会立即中止 - 但 @@error 为 0!在这些情况下,您可能希望使用 SET XACT_ABORT ON 运行。
  • 如果您想使用动态 SQL,请尝试在每个批处理中仅使用一个 SELECT,因为 @@error 仅保存最后执行的命令的状态。一批动态 SQL 中最可能出现的错误是语法错误,SET XACT_ABORT ON 不会处理这些错误。

【讨论】:

【解决方案2】:

我一直尝试使用的唯一技巧是:始终在靠近顶部的评论中包含示例用法。这对于测试您的 SP 也很有用。我喜欢包含最常见的示例 - 然后您甚至不需要 SQL 提示符或带有您最喜欢的调用的单独 .sql 文件,因为它就存储在服务器中(如果您存储了查看的过程,这将特别有用) sp_who 输出块或其他内容并采用一堆参数)。

类似:

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

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

【讨论】:

    【解决方案3】:
    1. 始终使用 SET NOCOUNT ON
    2. 如果您要执行两次或多次插入/更新/删除,请使用事务。
    3. 永远不要将你的 proc 命名为“sp_”。 SQL Server 将首先在主数据库中查找,而不是找到它,然后再在您的数据库中查找。如果您以不同的方式命名您的过程,SQL Server 将首先在您的数据库中查找。

    不好:

    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 现在会为您执行此操作,因此不会丢失错误条件。

    【讨论】:

    • 亲爱的 Simon Hughes 请你举一个例子,从编写程序的开始到结束的演示插入语句
    【解决方案4】:

    这是一个非常笼统的问题,但这里有一些建议:

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

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

    【讨论】:

      【解决方案5】:

      在 SQL Server 中,我总是放置一个语句,如果该过程存在则删除该过程,这样我就可以在开发过程中轻松地重新创建该过程。比如:

      IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'usp') 并输入 (N'P', N'PC')) 删除程序 usp

      【讨论】:

      • 你可以用以下方式简化它:IF OBJECT_ID(N'dbo.usp') IS NOT NULL DROP PROCEDURE dbo.usp GO CREATE PROCEDURE dbo.usp ...
      • 是的,alter 可以做同样的事情,但是如果你把它写成 alter,然后第一次把它放到服务器上,它就会失败。无论 proc alrady 是否存在,此过程都将起作用。
      • 好吧,问题在于,当您 DROP 程序时,您会丢失所有附带的设置。 ALTER PROC 保留所有安全设置,因此应该首选 ALTER。
      • 在我看来,更好的方法是更改​​您的代码以在开头包含相反的检查。如果它不存在,请快速创建它。然后添加您的权限,然后在其下方添加 Alter 语句。这样你就可以在任何地方运行它,无需不必要的删除/重新创建操作,也不会丢失任何设置。
      【解决方案6】:

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

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

      如果需要将检查写入 proc 以确保您的最终结果是正确的。我是一名 ETL 专家,在尝试将数据导入表之前,我总是编写我的过程以使数据得到清理和规范化。如果您是从用户界面执行操作,那么在 proc 中执行此操作可能并不那么重要,尽管我会在运行 proc 之前让用户界面进行检查以确保数据适合插入(例如检查以确保datefield 包含一个真实的日期,所有必填字段都有值等)

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

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

      不要在游标中做事,只是因为你想重用另一个只在一个记录上工作的存储过程。代码重用会导致性能问题,如果是坏事。

      如果您使用的是 case 语句或 if 语句,请确保您已经完成了可以触及每个可能分支的测试。你不测试的就是会失败的。

      【讨论】:

      • 这个:Do not do things in a cursor, just because you want to reuse another stored proc that only works on one record at time. 起初工作正常,一旦你获得真实的数据量,就会在 PROD 中杀死整个服务器。
      【解决方案7】:

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

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

      了解如何使用性能调优工具、了解执行计划的工作原理以及此类性质的事情是您进入“下一个级别”的方式

      【讨论】:

      • “存储过程只是存储的 T-SQL 查询。”存储过程远不止是存储的 T-SQL 查询。视图只是存储的 SQL 查询。
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

        基本的东西:

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

        【讨论】:

        • 为什么不从 SP 调用 SP?我总是出于必要而这样做;如果我不这样做就违反了 DRY 原则...?
        • DRY 适用于过程代码,而不是声明性代码。如果它是 sp 中的程序逻辑,我建议你将它移到另一个层次,这肯定是有意义的。
        • 另一个强烈的抑制因素。多级 ROLLBACK TRAN所以搞砸了。
        • @dkretz:SQL Server 上没有多级回滚。在任何一个会话中,只能有 1 个或 0 个事务处于活动状态。当有一个事务在运行时,任何新的 BEGIN TRAN 都没有结束;它只是将 1 添加到 @@TRANCOUNT 但仍然只有 1 个事务正在运行。在会话中从 ANYWHERE(甚至另一个过程)执行的 ROLLBACK TRAN 回滚当前活动的事务并将 @@TRANCOUNT 设置为 0。
        【解决方案10】:

        以下是一些最佳实践,

        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

        【讨论】:

        • 为什么要避免使用临时表?这不是明智的建议。如果使用得当,临时表是工具库中最好的东西之一。
        【解决方案11】:

        这里有一些代码可以证明 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;
        

        【讨论】:

          猜你喜欢
          • 2010-09-05
          • 2020-03-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-24
          • 2010-09-07
          • 2014-01-18
          相关资源
          最近更新 更多