AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 89777
Accepted
Oleg Dok
Oleg Dok
Asked: 2015-01-22 00:03:37 +0800 CST2015-01-22 00:03:37 +0800 CST 2015-01-22 00:03:37 +0800 CST

为什么以及如何有时在肯定有 Sch-M 锁的情况下出现“缺少表错误”?

  • 772

我有一个综合测试,它重现了我们在生产环境中遇到的一些错误。这是重现它的 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 端?

有人可以回答或指导我阅读我可以深入阅读并理解原因的技术论文吗?

sql-server sql-server-2005
  • 1 1 个回答
  • 249 Views

1 个回答

  • Voted
  1. Best Answer
    Geoff Patterson
    2015-02-03T18:52:30+08:002015-02-03T18:52:30+08:00

    非常有趣和棘手的问题。我找不到任何关于此行为的官方文档,而且我怀疑可能没有任何文档(尽管如果有人纠正我,我会很高兴!)。

    我的研究使我相信计划编译步骤容易受到这种竞争条件的影响。请注意,我能够毫无错误地运行您的测试查询一个小时,但如果我反复启动您的流程,有时会立即出现错误。当它确实遇到错误时,它总是会在计划编译时立即执行。或者,您可以将“OPTION RECOMPILE”添加到循环中的 COUNT(*),强制在每次试验时编译新计划。使用这种方法,每次运行您的脚本时,我几乎都能立即看到错误。

    我能够通过一系列似乎在每次试验中都会遇到错误的受控步骤来重现错误,从而无需设置循环并依赖随机性。

    我还提出了一个可能的修复方法(使用 ALTER TABLE...SWITCH),可以在您的生产环境中试用。现在,进入细节!

    以下是重现的步骤:

    -- Instructions: Process each step one at a time, following any instructions in that step
    
    -- (1) Initial setup: Clean any relics and create the test table
    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 CONSTRAINT PK_test PRIMARY KEY)
    GO
    INSERT test DEFAULT VALUES
    GO 2000
    
    -- (2) Run the COUNT(*)
    -- This will acquire the Sch-S lock, and we use HOLDLOCK to retain that lock.
    -- This simulates a race condition where this query is running at the time the first rename occurs.
    BEGIN TRANSACTION
    SELECT COUNT(*) FROM Test WITH (HOLDLOCK)
    GO
    
    -- (3) In another window, run the block of code that performs the renames
    -- This will be blocked, waiting for a Sch-M lock on "test" until we complete step 4 below
    CREATE TABLE TMP_test(Id INT PRIMARY KEY)
    INSERT TMP_test SELECT * FROM test
    EXEC sp_rename 'test', 'test1'
    EXEC sp_rename 'TMP_test', 'test'
    EXEC sp_rename 'test1', 'TMP_test'
    DROP TABLE TMP_test
    GO
    
    -- (4) Now, commit the original COUNT(*) and immediately fire off the query again
    -- We use OPTION (RECOMPILE) to make sure we need to compile a new query plan
    -- The COMMIT releases the Sch-S lock, allowing (3) to acquire the Sch-M lock
    -- This batch will now be waiting for the Sch-S lock again, on the same object_id,
    -- but that object_id will no longer point to the correct object by the time the lock
    -- is acquired.
    COMMIT
    SELECT COUNT(*) FROM Test OPTION (RECOMPILE)
    GO
    

    使用这些步骤,我们可以更深入地了解导致错误的原因。为此,我对以下事件进行了跟踪:SP:StmtStarting、SP:StmtCompleted、Lock:Acquired、Lock:Released。

    我发现按顺序发生以下情况(但省略了两者之间的一些细节):

    1. 第一个 COUNT(*) 获取一个 Sch-S 锁
    2. 第一个 COUNT(*) 获取一些统计信息的锁(大概是为了建立一个查询计划)
    3. 第一个 COUNT(*) 释放一个 Sch-S 锁(计划编译结束)
    4. 第一个COUNT(*)获取IS锁(开始执行),然后运行成功
    5. sp_rename 运行到它需要获取 Sch-M 锁的点,然后被第一个 COUNT(*) 阻塞,该 COUNT(*) 尚未提交
    6. 第一个 COUNT(*) 已提交
    7. sp_renames 获取 Sch-M 锁
    8. 第二个 COUNT(*) 请求一个 Sch-S 锁,并被添加到 sp_rename 后面的阻塞链
    9. sp_renames 完成(此时,第二个 COUNT(*) 正在等待 Sch-S 锁以获取不再是正确 object_id 的 object_id)
    10. 第二个 COUNT(*) 获取 Sch-S 锁
    11. 第二个 COUNT(*) 在 sys.sysschobjs 上获取一个 Sch-S 锁,大概是作为完整性检查,因为它检测到(或需要确认)Sch-S 锁有问题,它是在 object_id 上授予的成为“测试”
    12. 抛出无效对象名称“测试”错误

    但是,如果在不需要计划编译时(例如在运行循环时)发生类似的事件序列,则 SQL Server 实际上足够聪明,可以检测到 object_id 已更改。我也跟踪了那个案例,我发现了第二个 COUNT(*) 执行以下序列的情况

    1. 获取 Sch-S 锁(在曾经是“test”的 object_id 上)
    2. 在 sys.sysschobjs 上获取 Sch-S 锁
    3. 释放这两个锁
    4. 获取不同 object_id 上的 IS 锁;“测试”的新 object_id!
    5. 成功运行到完成

    因此,正如您所假设的那样,看起来确实存在这种自适应逻辑。但看起来它可能只用于查询执行,而不用于查询编译。

    正如承诺的那样,这里是第 (3) 部分的替代片段,它似乎提供了一种重命名表的方法来解决并发问题(至少在我的机器上是这样!):

    -- (3) In another window, run the block of code that performs the "renames"
    -- Instead of sp_rename, we use ALTER TABLE...SWITCH
    -- This appears to be a more robust way of performing the same logic
    CREATE TABLE test1(Id INT PRIMARY KEY)
    CREATE TABLE TMP_test(Id INT PRIMARY KEY)
    INSERT TMP_test SELECT * FROM test
    ALTER TABLE test SWITCH TO test1
    ALTER TABLE TMP_test SWITCH TO test
    ALTER TABLE test1 SWITCH TO TMP_test
    DROP TABLE TMP_test
    DROP TABLE test1
    GO
    

    最后,这是另一个我发现有用的链接,它可以帮助我思考如何在名称不断变化的表的上下文中考虑锁定:

    • http://www.sqlnotes.info/2011/12/02/sp_rename-causes-lock-leaking/
    • 5

相关问题

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

  • 从 SQL Server 2008 降级到 2005

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve