在用于数据子集交互式分析的脚本中,将查询结果存储到临时表中以供进一步分析通常很有用。
我的许多分析脚本都包含以下结构:
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;
Run Code Online (Sandbox Code Playgroud)
在 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;
Run Code Online (Sandbox Code Playgroud)
该语句正确处理了两种情况:
我编写的生产脚本总是使用这种方法,因为它在两种预期情况下都不会引发错误。
我的开发人员编写的一些等效脚本有时会使用异常处理来处理这两种情况:
BEGIN TRY DROP TABLE #Results END TRY BEGIN CATCH END CATCH
Run Code Online (Sandbox Code Playgroud)
我相信在数据库的世界里,请求许可总比寻求原谅好,所以这种方法让我感到不安。
第二种方法吞下错误,同时不采取任何措施来处理非异常行为(表不存在)。此外,有可能由于表不存在以外的原因而引发错误。
该聪明的猫头鹰提醒的是同一件事:
在这两种方法中,[
OBJECT_ID
method] 更难理解,但可能更好:使用 [BEGIN TRY
method],你冒着陷入错误错误的风险!
但它并没有解释实际风险是什么。
在实践中,该BEGIN TRY
方法从未在我维护的系统中引起问题,所以我很高兴它留在那里。
使用BEGIN TRY
方法管理临时表存在有哪些可能的危险?空的catch块可能会隐藏哪些意外错误?
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
Run Code Online (Sandbox Code Playgroud)
该批处理将失败并出现如下错误:
消息 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
Run Code Online (Sandbox Code Playgroud)
并再次运行。事务将提交,没有任何错误。