我有一个综合测试,它重现了我们在生产环境中遇到的一些错误。这是重现它的 2 个脚本:
第一
DBCC TRACEOFF(-1,3604,1200) WITH NO_INFOMSGS;
SET NOCOUNT ON;
IF @@TRANCOUNT > 0 ROLLBACK
IF object_id('test') IS NOT NULL
DROP TABLE test
IF object_id('TMP_test') IS NOT NULL
DROP TABLE TMP_test
IF object_id('test1') IS NOT NULL
DROP TABLE test1
CREATE TABLE test(Id INT IDENTITY PRIMARY KEY)
GO
INSERT test DEFAULT VALUES
GO 2000
WHILE 1 = 1
BEGIN
CREATE TABLE TMP_test(Id INT PRIMARY KEY)
INSERT TMP_test SELECT * FROM test
WAITFOR DELAY '0:00:00.1'
BEGIN TRAN
EXEC sp_rename 'test', 'test1'
EXEC sp_rename 'TMP_test', 'test'
EXEC sp_rename 'test1', 'TMP_test'
DROP TABLE TMP_test
commit
END
第二名
SET NOCOUNT ON;
DECLARE @c INT
WHILE 1 = 1
BEGIN
SELECT @c = COUNT(*) FROM Test IF @@ERROR <> 0 BREAK
SELECT @c = COUNT(*) FROM Test IF @@ERROR <> 0 BREAK
/* and repeat this 10-20 times more*/
SELECT @c = COUNT(*) FROM Test IF @@ERROR <> 0 BREAK
END
所以,问题是当我在一个会话中运行第一个脚本并让它运行,然后在单独的会话中运行第二个脚本时,我会得到这种类型的错误:
消息 208,级别 16,状态 1,第 13 行对象名称“测试”无效。
问题是 -为什么我会在第一个脚本的循环结束时看到此错误,COMMIT
而如果有ROLLBACK
?
我有一种感觉,它与情况有某种联系,当脚本提交时,仍然有同名的表,test
但它是一个不同的对象,第二个脚本必须重新编译自己。这没关系。但是为什么会出现 missed table 错误呢?AFAIK - 当我在事务中重命名表时 - 它持有 Sch-M 锁到 tran 端?
有人可以回答或指导我阅读我可以深入阅读并理解原因的技术论文吗?
非常有趣和棘手的问题。我找不到任何关于此行为的官方文档,而且我怀疑可能没有任何文档(尽管如果有人纠正我,我会很高兴!)。
我的研究使我相信计划编译步骤容易受到这种竞争条件的影响。请注意,我能够毫无错误地运行您的测试查询一个小时,但如果我反复启动您的流程,有时会立即出现错误。当它确实遇到错误时,它总是会在计划编译时立即执行。或者,您可以将“OPTION RECOMPILE”添加到循环中的 COUNT(*),强制在每次试验时编译新计划。使用这种方法,每次运行您的脚本时,我几乎都能立即看到错误。
我能够通过一系列似乎在每次试验中都会遇到错误的受控步骤来重现错误,从而无需设置循环并依赖随机性。
我还提出了一个可能的修复方法(使用 ALTER TABLE...SWITCH),可以在您的生产环境中试用。现在,进入细节!
以下是重现的步骤:
使用这些步骤,我们可以更深入地了解导致错误的原因。为此,我对以下事件进行了跟踪:SP:StmtStarting、SP:StmtCompleted、Lock:Acquired、Lock:Released。
我发现按顺序发生以下情况(但省略了两者之间的一些细节):
但是,如果在不需要计划编译时(例如在运行循环时)发生类似的事件序列,则 SQL Server 实际上足够聪明,可以检测到 object_id 已更改。我也跟踪了那个案例,我发现了第二个 COUNT(*) 执行以下序列的情况
因此,正如您所假设的那样,看起来确实存在这种自适应逻辑。但看起来它可能只用于查询执行,而不用于查询编译。
正如承诺的那样,这里是第 (3) 部分的替代片段,它似乎提供了一种重命名表的方法来解决并发问题(至少在我的机器上是这样!):
最后,这是另一个我发现有用的链接,它可以帮助我思考如何在名称不断变化的表的上下文中考虑锁定: