MS SQL 服务器 - 安全并发使用全局临时文件 table?

MS SQL Server - safe concurrent use of global temp table?

在 MS SQL 服务器中,我使用全局临时文件 table 来存储客户端传递的会话相关信息,然后在触发器中使用该信息。

因为同一个全局温度 table 可以在不同的会话中使用,当我想写入它时它可能存在也可能不存在(取决于之前使用它的所有先前会话是否已关闭) ,我正在检查全局温度 table 是否存在,这是我在写入之前创建的。

IF OBJECT_ID('tempdb..##VTT_CONTEXT_INFO_USER_TASK') IS NULL
  CREATE TABLE ##VTT_CONTEXT_INFO_USER_TASK (
    session_id    smallint,
    login_time    datetime,
    HstryUserName VDT_USERNAME,
    HstryTaskName VDT_TASKNAME,
  )

MERGE ##VTT_CONTEXT_INFO_USER_TASK As target
USING (SELECT @@SPID, @HstryUserName, @HstryTaskName) as source (session_id, HstryUserName, HstryTaskName)
ON (target.session_id = source.session_id)
WHEN MATCHED THEN
  UPDATE SET HstryUserName = source.HstryUserName, HstryTaskName = source.HstryTaskName
WHEN NOT MATCHED THEN
  INSERT VALUES (@@SPID, @LoginTime, source.HstryUserName, source.HstryTaskName);

问题是,在我检查 table 是否存在和 MERGE 语句之间,SQL 服务器可能会删除临时 table 如果所有会话都是在恰好关闭之前使用它(这实际上发生在我的测试中)。

是否有关于如何避免此类并发问题的最佳实践,即在检查其存在和后续使用之间不丢弃 table?

"global temporary table"和"trigger"的概念就是不要点击。表是永久性数据存储,它们的属性也是如此——包括触发器。当服务器 re-started 时,临时 table 将被丢弃。为什么会有人设计一个 permanent 代码块(触发器)依赖于 temporary 共享存储机制的系统?这似乎是失败的秘诀。

不要使用全局临时 table,而是使用真正的 table。如果愿意,可以在名称前面加上有用的前缀,例如 temp_。如果 table 由数据库共享,则将其放入所有代码都可以访问的数据库中。

创建一次 table 并将其保留在那里(删除行没问题)以便触发器代码可以访问它。

首先我要说的是,从长远来看,我会听从 Gordon 的建议,即我会采取必要的步骤在数据库中引入一个正常的 table 来存储需要的客户端应用程序信息可以在触发器中访问。

但是由于时间限制,现在这实际上是不可能的(需要数周时间才能获得新常态的必要正式批准 table),我想出了一个解决方案来防止 SQL 服务器在检查其存在与 MERGE 语句之间删除全局温度 table。

有一些关于全局临时 table 何时被 SQL 服务器删除的信息;我的个人测试表明,SQL 服务器会在创建它的会话关闭时丢弃全局温度 table,并且在 table 中更改数据的其他会话中启动的任何其他事务已完成。

我的解决方案是在检查全局温度 table 是否存在之前伪造数据更改。如果 table 在那一刻存在,那么 SQL 服务器将知道它需要保留它直到当前事务完成,并且在检查其存在后不能再删除它。代码现在看起来像这样(正确注释,因为它有点 hack):

-- Faking a delete on the table ensures that SQL Server will keep the table until the end of the transaction
-- Since ##VTT_CONTEXT_INFO_USER_TASK may actually not exist, we need to fake the delete inside TRY .. CATCH
-- FUTURE 2016, Feb 03: A cleaner solution would use a real table instead of a global temp table. 
BEGIN TRY
  -- Because schema errors are checked during compile, they cannot be caught using TRY, this can be done by wrapping the query in sp_executesql
  DECLARE @QueryText NVARCHAR(100) = 'DELETE ##VTT_CONTEXT_INFO_USER_TASK WHERE 0 = 1'
  EXEC sp_executesql @QueryText
END TRY
BEGIN CATCH
-- nothing to do here (see comment above)
END CATCH

IF OBJECT_ID('tempdb..##VTT_CONTEXT_INFO_USER_TASK') IS NULL
  CREATE TABLE ##VTT_CONTEXT_INFO_USER_TASK (
    session_id    smallint,
    login_time    datetime,
    HstryUserName VDT_USERNAME,
    HstryTaskName VDT_TASKNAME,
  )

MERGE ##VTT_CONTEXT_INFO_USER_TASK As target
USING (SELECT @@SPID, @HstryUserName, @HstryTaskName) as source (session_id, HstryUserName, HstryTaskName)
ON (target.session_id = source.session_id)
WHEN MATCHED THEN
  UPDATE SET HstryUserName = source.HstryUserName, HstryTaskName = source.HstryTaskName
WHEN NOT MATCHED THEN
  INSERT VALUES (@@SPID, @LoginTime, source.HstryUserName, source.HstryTaskName);

尽管我将其称为 "use it at your own risk" 解决方案,但它确实可以防止在其他会话中使用全局临时 table 影响其在当前会话中的使用,这是引起关注的问题我开始这个话题。

感谢大家的宝贵时间! (从文本格式编辑到回复)