BEGIN TRY DROP TABLE有什么危险?

时间:2012-09-12 13:46:35

标签: sql-server tsql

在用于交互式分析数据子集的脚本中,将查询结果存储到临时表中以进行进一步分析通常很有用。

我的许多分析脚本都包含以下结构:

CREATE TABLE #Results (
  a INT NOT NULL,
  b INT NOT NULL,
  c INT NOT NULL
);

INSERT INTO #Results (a, b, c)
SELECT a, b, c
FROM ...

SELECT *
FROM #Results;

在SQL Server中,临时表是连接范围的,因此查询结果在初始查询执行后仍然存在。当我想要分析的数据子集计算成本很高时,我使用此方法而不是使用表变量,因为子集在不同批次的查询中保持不变。

脚本的设置部分运行一次,然后根据需要运行以下查询(SELECT * FROM #Results是占位符)。

有时,我想刷新临时表中的数据子集,因此我再次运行整个脚本。一种方法是通过将脚本复制到Management Studio中的新查询窗口来创建新连接,我发现这很难管理。

相反,我通常的解决方法是在create语句之前添加一个条件drop语句,如下所示:

IF OBJECT_ID(N'tempdb.dbo.#Results', 'U') IS NOT NULL
BEGIN
  DROP TABLE #Results;
END;

此声明正确处理两种情况:

  1. 当表不存在时第一次运行:什么都不做。
  2. 在表存在的后续运行中:删除表。
  3. 我编写的生产脚本总是使用这种方法,因为它在两种预期的情况下都没有引起错误。

    我的开发人员编写的一些等效脚本有时会使用异常处理来处理这两种情况:

    BEGIN TRY DROP TABLE #Results END TRY BEGIN CATCH END CATCH
    

    我相信在数据库世界中,最好总是请求许可而不是寻求宽恕,所以这种方法让我感到不安。

    第二种方法吞并错误,而不采取任何操作来处理非异常行为(表不存在)。此外,由于表不存在的原因,可能会引发错误。

    Wise Owl警告同样的事情:

      

    在这两种方法中,[OBJECT_ID方法]更难以理解   可能更好:使用[BEGIN TRY方法],您将面临陷阱的风险   错误的错误!

    但它没有解释实际风险是什么。

    在实践中,BEGIN TRY方法从未在我维护的系统中引起问题,所以我很高兴它留在那里。

    使用BEGIN TRY方法管理临时表存在有哪些可能的危险?空捕获块可能隐藏了哪些意外错误?

4 个答案:

答案 0 :(得分:3)

What possible dangers? What unexpected errors are likely to be concealed?

如果try catch阻止在事务中,则会导致失败。

BEGIN
BEGIN TRANSACTION t1;
SELECT 1

BEGIN TRY DROP TABLE #Results END TRY BEGIN CATCH END CATCH

COMMIT TRANSACTION t1;
END

此批处理将失败并显示如下错误:

  

Msg 3930,Level 16,State 1,Line 7   当前事务无法提交,也无法支持写入日志文件的操作。回滚交易。   Msg 3998,Level 16,State 1,Line 1   在批处理结束时检测到不可提交的事务。该事务将被回滚。

Books Online记录了这种行为:

  

无法提交的交易和XACT_STATE

     

如果TRY块中生成的错误导致当前事务的状态无效,则该事务被分类为不可提交的事务。通常在TRY块之外结束事务的错误会导致事务在TRY块内发生错误时进入不可提交状态。不可提交的事务只能执行读操作或ROLLBACK TRANSACTION。事务不能执行任何会生成写操作或COMMIT TRANSACTION的Transact-SQL语句。

现在用测试方法替换TRY / Catch

BEGIN
BEGIN TRANSACTION t1;
SELECT 1

IF OBJECT_ID(N'tempdb.dbo.#Results', 'U') IS NOT NULL
BEGIN
  DROP TABLE #Results;
END;

COMMIT TRANSACTION t1;
END

然后再次运行.Transaction将提交而不会出现任何错误。

答案 1 :(得分:1)

更好的解决方案可能是使用表变量而不是临时表

即:

declare @results table( 
  a INT NOT NULL, 
  b INT NOT NULL, 
  c INT NOT NULL 
); 

答案 2 :(得分:0)

我还认为try块很危险,因为它可以隐藏意外问题。 Some programing languages can catch only selected errors并且不会捕获意外的,如果您的编程语言具有此功能,则使用它(T-SQL无法捕获特定错误)

对于您的场景,我可以通过此try catch块解释我与您完全一样的编码。

理想的行为是:

begin try
   drop table #my_temp_table
end try
begin catch __table_dont_exists_error__
end catch

但这不存在!然后你可以写一些想法:

begin try
   drop table #my_temp_table
end try
begin catch 
  declare @err_n int, @err_d varchar(MAX)
  SELECT 
    @err_n = ERROR_NUMBER() ,
    @err_d = ERROR_MESSAGE() ;
  IF @err_n <> 3701 
     raiserror( @err_d, 16, 1    )     
end catch

当错误删除表不同于“表不存在”时,这将引发事件。

请注意,对于您的问题,所有这些代码都不值得。但对其他方法可能有用。对于您的问题,优雅的解决方案是drop table only if exists或使用table variable

答案 3 :(得分:0)

不在你的问题中,但可能忽略的是临时表使用的资源。我总是把表放在脚本的末尾,这样它就不会占用资源。如果你在表中放置一百万行怎么办?然后我还在脚本开始时测试表以处理上次运行中出现错误并且表没有被删除的情况。如果要重用temp,那么至少要清除行。

表变量是另一种选择。它重量更轻,并且有局限性。如果要在查询连接中使用它,请避免使用表变量,因为查询优化器不处理表变量,因为它具有临时性。

SQL文档:

如果在单个存储过程或批处理中创建了多个临时表,则它们必须具有不同的名称。

如果在存储过程或应用程序中创建了可由多个用户同时执行的本地临时表,则数据库引擎必须能够区分不同用户创建的表。数据库引擎通过在每个本地临时表名称内部附加数字后缀来完成此操作。存储在tempdb中sysobjects表中的临时表的全名由CREATE TABLE语句中指定的表名和系统生成的数字后缀组成。要允许后缀,为本地临时名称指定的table_name不能超过116个字符。

临时表在超出范围时会自动删除,除非使用DROP TABLE:

显式删除

存储过程完成后,将自动删除在存储过程中创建的本地临时表。该表可以由创建该表的存储过程执行的任何嵌套存储过程引用。调用创建表的存储过程的进程无法引用该表。

所有其他本地临时表在当前会话结束时自动删除。

当创建表的会话结束且所有其他任务已停止引用它们时,将自动删除全局临时表。任务和表之间的关联仅在单个Transact-SQL语句的生命周期内维护。这意味着在创建会话结束时主动引用该表的最后一个Transact-SQL语句完成时,将删除全局临时表。