5

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

由于相同的全局临时表可以在不同的会话中使用,并且当我想写入它时它可能存在也可能不存在(取决于之前使用它的所有先前会话是否已关闭),我正在检查全局临时表存在基于我在写入之前创建的。

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);

问题是,在我检查表是否存在和MERGE语句之间,如果之前使用它的所有会话碰巧在那个确切的实例中关闭(这实际上发生在我的测试中),SQL Server 可能会删除临时表。

是否有关于如何避免这种并发问题的最佳实践,即在检查表是否存在和后续使用之间不会删除表?

4

2 回答 2

4

“全局临时表”和“触发器”的概念只是不要点击。表是永久性数据存储,它们的属性也是如此——包括触发器。重新启动服务器时会删除临时表。为什么有人会设计一个永久代码块(触发器)依赖于临时共享存储机制的系统?这似乎是失败的秘诀。

使用真实表代替全局临时表。如果您愿意,可以在名称前添加一个有用的前缀,例如temp_。如果该表正在由数据库共享,则将其放入所有代码都可以访问的数据库中。

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

于 2016-01-21T12:01:03.390 回答
4

我首先要说的是,从长远来看,我将遵循 Gordon 的建议,即我将采取必要的步骤在数据库中引入一个普通表来存储需要在触发器中访问的客户端应用程序信息。

但是由于时间限制,现在这实际上是不可能的(新的普通表需要几周的时间才能获得必要的正式批准),我想出了一个解决方案来防止 SQL Server 在检查它的过程中删除全局临时表。存在和MERGE陈述。

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

我的解决方案是在检查全局临时表是否存在之前就伪造全局临时表上的数据更改。如果此时表存在,SQL Server 将知道它需要保留它直到当前事务完成,并且在检查它是否存在之后不能再删除它。代码现在看起来像这样(正确注释,因为它是一种 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);

尽管我将其称为“使用风险自负”的解决方案,但它确实可以防止在其他会话中使用全局临时表影响其在当前会话中的使用,这是让我开始这个线程的担忧。

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

于 2016-02-05T15:50:25.977 回答