在MS SQL Server中,我使用全局临时表存储客户端传递的会话相关信息,然后在触发器中使用该信息。
由于同一个全局临时表可以在不同的会话中使用,当我想写入它时可能存在也可能不存在(取决于之前使用它的所有先前会话是否都已关闭),我是' m在我写入之前基于我创建的全局临时表存在进行检查。
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可能会删除临时表(这实际发生了在我的测试中。)
是否有关于如何避免这种并发问题的最佳实践,是否在检查其存在与其后续使用之间不删除表?
答案 0 :(得分:4)
“全局临时表”和“触发器”的概念不要单击。表是永久数据存储,它们的属性 - 包括触发器。重新启动服务器时将删除临时表。为什么有人会设计一个永久代码块(触发器)依赖于临时共享存储机制的系统?这似乎是失败的秘诀。
使用真实表,而不是全局临时表。如果您愿意,可以在名称前添加一个有用的前缀,例如temp_
。如果表由数据库共享,则将其放在所有代码都可以访问的数据库中。
创建一次表并将其留在那里(删除行很好),以便触发器代码可以访问它。
答案 1 :(得分:3)
我首先要说的是,从长远来看,我会遵循戈登的建议,即我将采取必要的步骤在数据库中引入一个普通表来存储需要在触发器中访问的客户端应用程序信息
但由于时间限制现在不可能实现这一点(需要花费数周的时间才能获得新常规表的必要正式批准),我想出了一个解决方案,以防止SQL Server丢弃全局临时表。检查它的存在和MERGE
语句。
有一些关于SQL Server何时删除全局临时表的信息;我的个人测试表明,SQL Server在创建它的会话关闭的时候删除了一个全局临时表,并且在其他会话中启动了更改该表中数据的任何其他事务。
我的解决方案是在检查其存在之前,假设全局临时表上的数据更改。如果该表存在于那一刻,则SQL Server将知道它需要保留它直到当前事务完成,并且在检查其存在之后它不能再被丢弃。代码现在看起来像这样(正确评论,因为它是一种黑客攻击):
-- 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);
虽然我称之为“使用它自负风险”解决方案,但它确实可以防止在其他会话中使用全局临时表影响其在当前会话中的使用,这是让我开始这个问题的关注点线程。
谢谢大家的时间! (从文本格式编辑到回复)