【问题标题】:SQL Server - stop or break execution of a SQL scriptSQL Server - 停止或中断 SQL 脚本的执行
【发布时间】:2010-10-14 03:17:47
【问题描述】:

有没有办法立即停止 SQL 服务器中 SQL 脚本的执行,例如“break”或“exit”命令?

我有一个脚本在开始插入之前会进行一些验证和查找,如果任何验证或查找失败,我希望它停止。

【问题讨论】:

    标签: sql sql-server scripting exit


    【解决方案1】:

    非常感谢这里的所有其他人和我读过的其他帖子。 但在@jaraics 回答之前,没有什么能满足我的所有需求。

    我见过的大多数答案都忽略了多批次的脚本。他们忽略了 SSMS 和 SQLCMD 中的双重用法。我的脚本在 SSMS 中完全可以运行——但我希望 F5 预防,这样他们就不会意外删除现有的一组对象。

    SET PARSEONLY ON 工作得很好,可以防止不需要的 F5。但是你不能使用 SQLCMD 运行。

    另一件事让我慢了一阵子,当出现错误时,批处理将如何跳过任何进一步的命令 - 所以我的 SET NOCOUNT ON 被跳过,因此脚本仍然运行。

    无论如何,我稍微修改了 jaraics 的答案:(在这种情况下,我还需要一个数据库才能从命令行激活)

    -----------------------------------------------------------------------
    -- Prevent accidental F5
    -- Options:
    --     1) Highlight everything below here to run
    --     2) Disable this safety guard
    --     3) or use SQLCMD
    -----------------------------------------------------------------------
    set NOEXEC OFF                             -- Reset in case it got stuck ON
    set CONTEXT_INFO  0x1                      -- A 'variable' that can pass batch boundaries
    GO                                         -- important !
    if $(SQLCMDDBNAME) is not null
        set CONTEXT_INFO 0x2                   -- If above line worked, we're in SQLCMD mode
    GO                                         -- important !
    if CONTEXT_INFO()<>0x2 
    begin
        select 'F5 Pressed accidentally.'
        SET NOEXEC ON                          -- skip rest of script
    END
    GO                                         -- important !
    -----------------------------------------------------------------------
    
    < rest of script . . . . . >
    
    
    GO
    SET NOEXEC OFF
    print 'DONE'
    

    【讨论】:

      【解决方案2】:

      将其包含在try catch块中,然后将执行转移到catch。

      BEGIN TRY
          PRINT 'This will be printed'
          RAISERROR ('Custom Exception', 16, 1);
          PRINT 'This will not be printed'
      END TRY
      BEGIN CATCH
          PRINT 'This will be printed 2nd'
      END CATCH;
      

      【讨论】:

        【解决方案3】:

        过去我们使用以下...效果最好:

        RAISERROR ('Error! Connection dead', 20, 127) WITH LOG
        

        【讨论】:

          【解决方案4】:

          我一直在这里使用RETURN,使用脚本或Stored Procedure

          如果你在其中,请确保ROLLBACK 事务,否则RETURN 将立即导致打开未提交事务

          【讨论】:

          • 不适用于包含多个批处理(GO 语句)的脚本 - 请参阅我的答案以了解如何执行此操作。
          • RETURN 只是退出当前的语句块。如果您在 IF END 块中,则在 END 之后将继续执行。这意味着您不能在测试某些条件后使用 RETURN 来结束执行,因为您将始终处于 IF END 块中。
          【解决方案5】:

          这些都不适用于“GO”语句。在这段代码中,无论严重性是 10 还是 11,都会得到最终的 PRINT 语句。

          测试脚本:

          -- =================================
          PRINT 'Start Test 1 - RAISERROR'
          
          IF 1 = 1 BEGIN
              RAISERROR('Error 1, level 11', 11, 1)
              RETURN
          END
          
          IF 1 = 1 BEGIN
              RAISERROR('Error 2, level 11', 11, 1)
              RETURN
          END
          GO
          
          PRINT 'Test 1 - After GO'
          GO
          
          -- =================================
          PRINT 'Start Test 2 - Try/Catch'
          
          BEGIN TRY
              SELECT (1 / 0) AS CauseError
          END TRY
          BEGIN CATCH
              SELECT ERROR_MESSAGE() AS ErrorMessage
              RAISERROR('Error in TRY, level 11', 11, 1)
              RETURN
          END CATCH
          GO
          
          PRINT 'Test 2 - After GO'
          GO
          

          结果:

          Start Test 1 - RAISERROR
          Msg 50000, Level 11, State 1, Line 5
          Error 1, level 11
          Test 1 - After GO
          Start Test 2 - Try/Catch
           CauseError
          -----------
          
          ErrorMessage
          --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
          Divide by zero error encountered.
          
          Msg 50000, Level 11, State 1, Line 10
          Error in TRY, level 11
          Test 2 - After GO
          

          完成这项工作的唯一方法是编写不带GO 语句的脚本。有时这很容易。有时这是相当困难的。 (使用 IF @error &lt;&gt; 0 BEGIN ... 之类的东西。)

          【讨论】:

          • CREATE PROCEDURE 等无法做到这一点。请参阅我的答案以获得解决方案。
          • Blogbeard 的解决方案很棒。我已经使用 SQL Server 多年了,这是我第一次看到这个。
          【解决方案6】:

          raiserror 方法

          raiserror('Oh no a fatal error', 20, -1) with log
          

          这将终止连接,从而停止脚本的其余部分运行。

          请注意,20 或更高级别的严重性和WITH LOG 选项都是它以这种方式工作所必需的。

          这甚至适用于 GO 语句,例如。

          print 'hi'
          go
          raiserror('Oh no a fatal error', 20, -1) with log
          go
          print 'ho'
          

          会给你输出:

          hi
          Msg 2745, Level 16, State 2, Line 1
          Process ID 51 has raised user error 50000, severity 20. SQL Server is terminating this process.
          Msg 50000, Level 20, State 1, Line 1
          Oh no a fatal error
          Msg 0, Level 20, State 0, Line 0
          A severe error occurred on the current command.  The results, if any, should be discarded.
          

          请注意,'ho' 没有打印出来。

          注意事项:

          • 这仅在您以 admin('sysadmin' 角色)身份登录时有效,并且您也没有数据库连接。
          • 如果您不是以管理员身份登录,RAISEERROR() 调用本身将失败脚本将继续执行
          • 使用 sqlcmd.exe 调用时,会报退出码 2745。

          参考:http://www.mydatabasesupport.com/forums/ms-sqlserver/174037-sql-server-2000-abort-whole-script.html#post761334

          noexec 方法

          另一种适用于 GO 语句的方法是 set noexec on。这会导致脚本的其余部分被跳过。它不会终止连接,但您需要再次关闭noexec 才能执行任何命令。

          例子:

          print 'hi'
          go
          
          print 'Fatal error, script will not continue!'
          set noexec on
          
          print 'ho'
          go
          
          -- last line of the script
          set noexec off -- Turn execution back on; only needed in SSMS, so as to be able 
                         -- to run this script again in the same session.
          

          【讨论】:

          • 太棒了!这有点像“大棒”方法,但有时你真的需要它。请注意,它需要严重性 20(或更高)和“WITH LOG”。
          • 请注意,使用 noexec 方法,脚本的其余部分仍会被解释,因此您仍然会遇到编译时错误,例如列不存在。如果您想通过跳过某些代码来有条件地处理涉及丢失列的已知架构更改,我知道的唯一方法是在 sqlcommand 模式下使用 :r 来引用外部文件。
          • noexec 很棒。非常感谢!
          • 我正在尝试这种方法,但当我意识到时没有得到正确的结果... raiserror 中只有一个 E...
          • 如果您想在将作为非系统管理员用户运行的脚本中使用它,请确保将严重性设置为 18 或更低。该脚本将继续执行。
          【解决方案7】:

          您可以使用GOTO 语句更改执行流程:

          IF @ValidationResult = 0
          BEGIN
              PRINT 'Validation fault.'
              GOTO EndScript
          END
          
          /* our code */
          
          EndScript:
          

          【讨论】:

          • 使用 goto 是一种可接受的异常处理方式。减少变量和嵌套的数量并且不会导致断开连接。它可能比 SQL Server 脚本允许的过时异常处理更可取。
          • 就像这里的所有其他建议一样,如果“我们的代码”包含“GO”语句,这将不起作用。
          • 奇怪的是,这仍然解析了脚本;我收到“关键字'with'附近的语法错误”错误,但我能够阻止脚本在没有系统管理员的情况下运行(在删除所有'GO'命令之后)。谢谢。
          【解决方案8】:

          在 SQL 2012+ 中,您可以使用THROW

          THROW 51000, 'Stopping execution because validation failed.', 0;
          PRINT 'Still Executing'; -- This doesn't execute with THROW
          

          来自 MSDN:

          引发异常并将执行转移到 TRY...CATCH 构造的 CATCH 块...如果 TRY...CATCH 构造不可用,则结束会话。设置引发异常的行号和过程。严重性设置为 16。

          【讨论】:

          • THROW 是为了替换 RAISERROR,但是你不能用它来阻止在同一个脚本文件中的后续批处理。
          • 正确@NReilingh。这就是 Blorgbeard 的答案确实是唯一解决方案的地方。不过,它确实需要系统管理员(严重级别 20),如果脚本中没有多个批处理,它会相当笨拙。
          • 如果您还想取消当前事务,请设置 xact abort on。
          【解决方案9】:

          您可以使用 GOTO 语句。尝试这个。这对你来说已经用完了。

          WHILE(@N <= @Count)
          BEGIN
              GOTO FinalStateMent;
          END
          
          FinalStatement:
               Select @CoumnName from TableName
          

          【讨论】:

          • GOTO 应该是一种不好的编码习惯,建议使用“TRY..CATCH”,因为它是自 SQL Server 2008 以来引入的,随后是 2012 年的 THROW。
          【解决方案10】:

          如果您只是在 Management Studio 中执行脚本,并且希望在第一个错误时停止执行或回滚事务(如果使用),那么我认为最好的方法是使用 try catch 块(SQL 2005 及更高版本)。 如果您正在执行脚本文件,这在 Management Studio 中效果很好。 存储过程也可以一直使用它。

          【讨论】:

          【解决方案11】:

          这是我的解决方案:

          ...

          BEGIN
              raiserror('Invalid database', 15, 10)
              rollback transaction
              return
          END
          

          【讨论】:

            【解决方案12】:

            我不会使用 RAISERROR——SQL 有可用于此目的的 IF 语句。进行验证和查找并设置局部变量,然后使用 IF 语句中的变量值来使插入有条件。

            您不需要检查每个验证测试的可变结果。你通常可以只用一个标志变量来确认所有条件都通过了:

            declare @valid bit
            
            set @valid = 1
            
            if -- Condition(s)
            begin
              print 'Condition(s) failed.'
              set @valid = 0
            end
            
            -- Additional validation with similar structure
            
            -- Final check that validation passed
            if @valid = 1
            begin
              print 'Validation succeeded.'
            
              -- Do work
            end
            

            即使您的验证更复杂,您也应该只需要在最终检查中包含几个标志变量。

            【讨论】:

            • 是的,我在脚本的其他部分使用了 IF,但我不想在尝试插入之前检查每个局部变量。我宁愿让整个脚本停止,并强制用户检查输入。 (这只是一个快速而肮脏的脚本)
            • 我不太清楚为什么这个答案被标记了,因为它在技术上是正确的,而不是发帖人“想要”做的。
            • Begin..End 中是否可以有多个块?含义声明;走;陈述;走;等等等等?我遇到了错误,我想这可能是原因。
            • 这比RAISERROR 可靠得多,尤其是在您不知道谁将运行脚本以及拥有什么权限的情况下。
            • @John Sansom:我在这里看到的唯一问题是,如果您尝试分支 GO 语句,则 IF 语句不起作用。如果您的脚本依赖 GO 语句(例如 DDL 语句),这将是一个大问题。这是一个没有第一个 go 语句的示例:declare @i int = 0; if @i=0 begin select '1st stmt in IF block' go end else begin select 'ELSE here' end go
            【解决方案13】:

            我使用事务成功扩展了 noexec 开/关解决方案,以便以全有或全无的方式运行脚本。

            set noexec off
            
            begin transaction
            go
            
            <First batch, do something here>
            go
            if @@error != 0 set noexec on;
            
            <Second batch, do something here>
            go
            if @@error != 0 set noexec on;
            
            <... etc>
            
            declare @finished bit;
            set @finished = 1;
            
            SET noexec off;
            
            IF @finished = 1
            BEGIN
                PRINT 'Committing changes'
                COMMIT TRANSACTION
            END
            ELSE
            BEGIN
                PRINT 'Errors occured. Rolling back changes'
                ROLLBACK TRANSACTION
            END
            

            显然编译器“理解”IF 中的@finished 变量,即使出现错误并且执行被禁用。但是,仅当未禁用执行时,该值才设置为 1。因此,我可以很好地提交或回滚相应的事务。

            【讨论】:

            • 我不明白。我按照说明进行操作。我在每次 GO 后输入了以下 SQL。 IF (XACT_STATE()) &lt;&gt; 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; 但执行从未停止,我最终引发了三个“回滚事务”错误。有什么想法吗?
            【解决方案14】:

            进一步完善 Sglasses 方法,以上行强制使用 SQLCMD 模式,如果不使用 SQLCMD 模式,则终止脚本或使用:on error exit 退出任何错误
            CONTEXT_INFO 用于跟踪国家。

            SET CONTEXT_INFO  0x1 --Just to make sure everything's ok
            GO 
            --treminate the script on any error. (Requires SQLCMD mode)
            :on error exit 
            --If not in SQLCMD mode the above line will generate an error, so the next line won't hit
            SET CONTEXT_INFO 0x2
            GO
            --make sure to use SQLCMD mode ( :on error needs that)
            IF CONTEXT_INFO()<>0x2 
            BEGIN
                SELECT CONTEXT_INFO()
                SELECT 'This script must be run in SQLCMD mode! (To enable it go to (Management Studio) Query->SQLCMD mode)\nPlease abort the script!'
                RAISERROR('This script must be run in SQLCMD mode! (To enable it go to (Management Studio) Query->SQLCMD mode)\nPlease abort the script!',16,1) WITH NOWAIT 
                WAITFOR DELAY '02:00'; --wait for the user to read the message, and terminate the script manually
            END
            GO
            
            ----------------------------------------------------------------------------------
            ----THE ACTUAL SCRIPT BEGINS HERE-------------
            

            【讨论】:

            • 这是我发现解决无法中止脚本的 SSMS 疯狂问题的唯一方法。但是我在开头添加了“SET NOEXEC OFF”,如果不是在 SQLCMD 模式下,则添加“SET NOEXEC ON”,否则实际脚本将继续运行,除非您使用日志在级别 20 引发错误。
            • !!!!!!!!!!!!!谢谢 !!!!!!!!!!!!我见过的大多数答案都忽略了多批次的脚本。他们忽略了 SSMS 和 SQLCMD 中的双重用法。我的脚本在 SSMS 中完全可以运行——但我想要一个 F5 预防措施,这样他们就不会意外删除现有的一组对象。 SET PARSEONLY ON 工作得很好。但是你不能用 SQLCMD 运行。我也没有看到关于 SET NOCOUNT ON 当同一批中的任何东西都没有编译时不起作用的评论——这让我在一段时间内横盘整理。我在下面的答案中对此添加了一点点。
            【解决方案15】:

            如果你可以使用SQLCMD模式,那么咒语

            :on error exit
            

            (包括冒号)将导致 RAISERROR 实际停止脚本。例如,

            :on error exit
            
            IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SOMETABLE]') AND type in (N'U')) 
                RaisError ('This is not a Valid Instance Database', 15, 10)
            GO
            
            print 'Keep Working'
            

            将输出:

            Msg 50000, Level 15, State 10, Line 3
            This is not a Valid Instance Database
            ** An error was encountered during execution of batch. Exiting.
            

            并且批处理将停止。如果没有打开 SQLCMD 模式,你会得到关于冒号的解析错误。不幸的是,它并不是完全安全的,就好像脚本在没有 SQLCMD 模式的情况下运行一样,SQL Managment Studio 甚至可以轻松克服解析时间错误!不过,如果您从命令行运行它们,这很好。

            【讨论】:

            • 很好的评论,谢谢。我会补充一点,在 SSMS 中,SQLCmd 模式是在 Query 菜单下切换的。
            • 这很有用 - 意味着您在运行时不需要 -b 选项
            • 然后是咒语……但是我该如何施放魔法飞弹?!
            • 完美。不需要系统管理员超额外的用户权限
            【解决方案16】:

            谢谢你的回答!

            raiserror() 工作正常,但您不应忘记return 语句,否则脚本将继续运行而不会出错! (因此 raiserror 不是“throwerror” ;-))当然如果有必要会进行回滚!

            raiserror() 很高兴告诉执行脚本的人出了点问题。

            【讨论】:

              【解决方案17】:

              我建议您将适当的代码块包装在 try catch 块中。然后,您可以使用严重性为 11 的 Raiserror 事件,以便根据需要中断到 catch 块。如果您只想引发错误但在 try 块内继续执行,请使用较低的严重性。

              有意义吗?

              干杯, 约翰

              [已编辑以包含 BOL 参考]

              http://msdn.microsoft.com/en-us/library/ms175976(SQL.90).aspx

              【讨论】:

              • 我从未见过 SQL 中的 try-catch - 你介意发布一个简单的例子来说明你的意思吗?
              • 这是 2005 年的新功能。开始尝试 { sql_statement | statement_block } 结束尝试开始捕获 { sql_statement | statement_block } END CATCH [ ; ]
              • @Andy:已添加参考,包括示例。
              • TRY-CATCH 块不允许 GO 进入 itelf。
              【解决方案18】:

              您可以将 SQL 语句包装在 WHILE 循环中,并在需要时使用 BREAK

              WHILE 1 = 1
              BEGIN
                 -- Do work here
                 -- If you need to stop execution then use a BREAK
              
              
                  BREAK; --Make sure to have this break at the end to prevent infinite loop
              END
              

              【讨论】:

              • 我有点喜欢这个外观,它似乎比引发错误好一点。绝对不想忘记最后的休息!
              • 您也可以使用变量并立即将其设置在循环顶部以避免“分裂”。 DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; END 更冗长,但见鬼,反正它是 TSQL ;-)
              • 这就是一些人执行 goto 的方式,但它比 goto 更令人困惑。
              • 这种方法可以防止意外的偶然 GO。赞赏。
              【解决方案19】:

              只需使用 RETURN(它可以在存储过程的内部和外部工作)。

              【讨论】:

              • 在脚本中,不能像在存储过程中那样使用值执行 RETURN,但可以执行 RETURN。
              • 不,它只会终止直到下一个 GO 下一批(在 GO 之后)将照常运行
              • 假设是危险的,因为它会在下一个 GO 之后继续。
              • GO 是脚本终止符或分隔符;这不是 SQL 代码。 GO 只是向您用来向数据库引擎发送命令的客户端的指令,即新脚本在 GO 分隔符之后启动。
              • 这个答案根本没有帮助解决这个问题,即“有没有办法立即停止执行 SQL 脚本”。
              【解决方案20】:

              这是一个存储过程吗?如果是这样,我认为您可以只做一个返回,例如“返回NULL”;

              【讨论】:

              • 感谢您的回答,很高兴知道,但在这种情况下,它不是存储过程,只是一个脚本文件
              • @Gordon 并非总是如此(我在这里搜索)。查看其他答案(一方面,GO 出错了)
              【解决方案21】:

              你可以使用RAISERROR

              【讨论】:

              • 这对我来说毫无意义——如果在插入发生之前可以进行验证,那么引发一个可避免的错误(假设我们在这里讨论的是引用验证)是一种可怕的方法。
              • raiserror 可用作具有低严重性设置的信息性消息。
              • 除非满足接受的答案中规定的某些条件,否则脚本将继续运行。
              猜你喜欢
              • 1970-01-01
              • 2020-03-27
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-05-11
              • 2019-03-18
              相关资源
              最近更新 更多