我也可以在我的机器上 100% 地重现这个。(见文末注释)
问题的要点是您正在S
对系统表行进行锁定,tempdb
这可能与内部tempdb
清理事务所需的锁定发生冲突。
当此清理工作分配给拥有S
锁的同一会话时,可能会发生无限期挂起。
为了避免这个问题,你需要停止引用system
里面的对象tempdb
。
可以在根本不引用任何外部表的情况下创建数字表。以下内容不需要读取基表行,因此也不需要锁。
WITH Ten(N) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM Ten T10,
Ten T100,
Ten T1000,
Ten T10000,
Ten T100000,
Ten T1000000
重现步骤
首先创建一个过程
CREATE PROC P
AS
SET NOCOUNT ON;
DECLARE @T TABLE (X INT)
GO
然后重新启动 SQL 服务并在一个连接中执行
WHILE NOT EXISTS(SELECT *
FROM sys.dm_os_waiting_tasks
WHERE session_id = blocking_session_id)
BEGIN
/*This will cause the problematic droptemp transactions*/
EXEC sp_recompile 'P'
EXEC P
END;
SELECT *
FROM sys.dm_os_waiting_tasks
WHERE session_id = blocking_session_id
然后在另一个连接中运行
USE tempdb;
SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO #T
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;
DROP TABLE #T
填充 Numbers 表的查询似乎设法进入活锁情况,内部系统事务清理临时对象(如表变量)。
我设法以这种方式阻止了会话 id 53。它被无限期地封锁。的输出sp_WhoIsActive
显示此 spid 几乎所有时间都处于暂停状态。在连续运行中,reads
列中的数字会增加,但其他列中的值基本保持不变。
等待持续时间没有显示出增加的模式,尽管表明它必须在再次被阻塞之前定期解除阻塞。
SELECT *
FROM sys.dm_os_waiting_tasks
WHERE session_id = blocking_session_id
退货
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| waiting_task_address | session_id | exec_context_id | wait_duration_ms | wait_type | resource_address | blocking_task_address | blocking_session_id | blocking_exec_context_id | resource_description |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| 0x00000002F2C170C8 | 53 | 0 | 86 | LCK_M_X | 0x00000002F9B13040 | 0x00000002F2C170C8 | 53 | NULL | keylock hobtid=281474978938880 dbid=2 id=lock2f9ac8880 mode=U associatedObjectId=281474978938880 |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
在资源描述中使用 id
SELECT o.name
FROM sys.allocation_units au WITH (NOLOCK)
INNER JOIN sys.partitions p WITH (NOLOCK)
ON au.container_id = p.partition_id
INNER JOIN sys.all_objects o WITH (NOLOCK)
ON o.object_id = p.object_id
WHERE allocation_unit_id = 281474978938880
退货
+------------+
| name |
+------------+
| sysschobjs |
+------------+
跑步
SELECT resource_description,request_status
FROM sys.dm_tran_locks
WHERE request_session_id = 53 AND request_status <> 'GRANT'
退货
+----------------------+----------------+
| resource_description | request_status |
+----------------------+----------------+
| (246708db8c1f) | CONVERT |
+----------------------+----------------+
通过 DAC 连接并运行
SELECT id,name
FROM tempdb.sys.sysschobjs WITH (NOLOCK)
WHERE %%LOCKRES%% = '(246708db8c1f)'
退货
+-------------+-----------+
| id | name |
+-------------+-----------+
| -1578606288 | #A1E86130 |
+-------------+-----------+
好奇那是什么
SELECT name,user_type_id
FROM tempdb.sys.columns
WHERE object_id = -1578606288
退货
+------+--------------+
| name | user_type_id |
+------+--------------+
| X | 56 |
+------+--------------+
这是存储过程使用的表变量中的列名。
跑步
SELECT request_mode,
request_status,
request_session_id,
request_owner_id,
lock_owner_address,
t.transaction_id,
t.name,
t.transaction_begin_time
FROM sys.dm_tran_locks l
JOIN sys.dm_tran_active_transactions t
ON l.request_owner_id = t.transaction_id
WHERE resource_description = '(246708db8c1f)'
退货
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| request_mode | request_status | request_session_id | request_owner_id | lock_owner_address | transaction_id | name | transaction_begin_time |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| U | GRANT | 53 | 227647 | 0x00000002F1EF6800 | 227647 | droptemp | 2013-11-24 18:36:28.267 |
| S | GRANT | 53 | 191790 | 0x00000002F9B16380 | 191790 | SELECT INTO | 2013-11-24 18:21:30.083 |
| X | CONVERT | 53 | 227647 | 0x00000002F9B12FC0 | 227647 | droptemp | 2013-11-24 18:36:28.267 |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
所以事务在与表变量有关的行上SELECT INTO
持有一个锁。由于此锁冲突,事务无法获得该行的锁。S
tempdb.sys.sysschobjs
#A1E86130
droptemp
X
S
重复运行此查询会发现transaction_id
fordroptemp
事务重复更改。
我推测 SQL Server 必须在用户 spid 上分配这些内部事务,并在执行用户工作之前对它们进行优先级排序。因此,会话 id 53 卡在启动droptemp
事务的恒定循环中,被在同一 spid 上运行的用户事务阻止。回滚内部事务,然后无限期地重复该过程。
这通过在 spid 挂起后跟踪 SQL Server Profiler 中的各种锁定和事务事件来证实。

我还跟踪了之前的锁定事件。
锁定事件阻塞

SELECT INTO
大部分由事务取出的共享密钥锁sysschobjs
都会立即释放。例外是第一次锁定(246708db8c1f)
。
这是有道理的,因为该计划显示了嵌套循环扫描,[sys].[sysschobjs].[clst] [o]
并且因为临时对象被赋予负的 objectid,它们将是扫描顺序中遇到的第一行。
我还遇到了 OP 中描述的情况,首先运行三路交叉连接似乎允许四路连接成功。
事务跟踪中的前几个事件SELECT INTO
有一个完全不同的模式。

这是在服务重新启动之后,因此文本数据列中的锁定资源值不能直接比较。
而不是保留第一个键上的锁,然后获取和释放后续键的模式,它似乎获得了更多的锁,而不是最初释放它们。
我认为执行策略必须有一些差异来避免这个问题。
更新
我提出的关于此的连接项尚未标记为已修复,但我现在使用的是 SQL Server 2012 SP2,现在只能重现临时自我阻塞而不是永久。我仍然得到自我阻塞,但在成功执行事务的尝试失败后,droptemp
它似乎又回到了处理用户事务。之后提交系统事务然后成功执行。仍然在同一个spid上。(在一个示例中进行了八次尝试。我不确定这是否会一直重复)