5

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

我的许多分析脚本都包含这种结构:

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. 在表确实存在的后续运行中:删除表。

我编写的生产脚本将始终使用这种方法,因为它在两种预期情况下都不会引发错误。

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

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

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

第二种方法在不采取任何措施处理非异常行为(表不存在)时吞下错误。此外,由于表不存在以外的其他原因,可能会引发错误。

聪明的猫头鹰警告同样的事情:

在这两种方法中,[ OBJECT_IDmethod] 更难理解,但可能更好:使用 [ BEGIN TRYmethod],您冒着捕获错误错误的风险!

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

实际上,该BEGIN TRY方法从未在我维护的系统中引起问题,因此我很高兴它留在那里。

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

4

4 回答 4

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

此批处理将失败,并出现如下错误:

消息 3930,级别 16,状态 1,第 7 行当前事务无法提交,并且无法支持写入日志文件的操作。回滚事务。消息 3998,级别 16,状态 1,第 1 行在批处理结束时检测到不可提交事务。事务被回滚。

联机丛书记录了这种行为:

不可提交事务和 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

并再次运行。事务将提交而不会出现任何错误。

于 2012-09-14T01:14:39.940 回答
1

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

IE:

declare @results table( 
  a INT NOT NULL, 
  b INT NOT NULL, 
  c INT NOT NULL 
); 
于 2012-09-12T14:04:10.723 回答
0

我也认为一个try块是危险的,因为它可以隐藏一个意想不到的问题。某些编程语言只能捕获选定的错误而不捕获意外错误,如果您的编程语言具有此功能,则使用它(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

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

请注意,对于您的问题,所有这些代码都不值得。但可能对其他方法有用。对于您的问题,优雅的解决方案是仅在存在或使用表变量时才删除表。

于 2012-09-12T14:04:30.680 回答
0

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

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

SQL 文档:

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

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

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

当存储过程完成时,在存储过程中创建的本地临时表会自动删除。该表可以被创建该表的存储过程执行的任何嵌套存储过程引用。调用创建该表的存储过程的进程不能引用该表。

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

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

于 2012-09-12T16:43:17.250 回答