Аз мога също може да възпроизведе това 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
показва, че този паяк прекарва почти цялото си време в спряно състояние. При последователни изпълнения числата в 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 |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
Използване на идентификатора в описанието на ресурса
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
за droptemp
транзакцията многократно се променя.
Предполагам, че SQL Server трябва да разпредели тези вътрешни транзакции на потребителски spids и да ги приоритизира, преди да извърши работата на потребителя. Така че идентификаторът на сесията 53 е блокиран в постоянен цикъл, където стартира droptemp
транзакция, се блокира от потребителската транзакция, изпълнявана на същия spid. Връща назад вътрешната транзакция, след което повтаря процеса за неопределено време.
Това се потвърждава от проследяване на различните заключващи и транзакционни събития в SQL Server Profiler, след като spid се окачва.
Също така проследих заключващите събития преди това.
Блокиране на заключващи събития
Повечето от ключалките със споделен ключ са извадени от SELECT INTO
транзакция на ключове в sysschobjs
освободете незабавно. Изключение е първото заключване на (246708db8c1f)
.
Това има известен смисъл, тъй като планът показва сканиране на вложени цикли на [sys].[sysschobjs].[clst] [o]
и тъй като на временните обекти се дават отрицателни objectid, те ще бъдат първите срещани редове в реда на сканиране.
Също така се сблъсках със ситуацията, описана в OP, където стартирането на трипосочно кръстосано съединение първо изглежда позволява на четирипосочното едно да успее.
Първите няколко събития в трасирането за SELECT INTO
транзакция има съвсем различен модел.
Това беше след рестартиране на услугата, така че стойностите на ресурса за заключване в колоната с текстови данни не са директно сравними.
Вместо да се запази ключалката на първия ключ и след това модел на придобиване и освобождаване на следващите ключове, изглежда се придобиват много повече брави, без да се освобождават първоначално.
Предполагам, че трябва да има някаква разлика в стратегията за изпълнение, която избягва проблема.
Актуализиране
Елементът Connect, който повдигнах за това
не е маркиран като фиксиран, но сега съм на SQL Server 2012 SP2 и сега мога да възпроизвеждам само временно самоблокиране, а не постоянно. Все още получавам самоблокиране, но след известен брой неуспешни опити да изпълня droptemp
транзакцията е успешна, изглежда, че се връща към обработката на потребителската транзакция. След това се ангажира системната транзакция, след което се изпълнява успешно. Все още на същия spid. (осем опита в едно примерно изпълнение. Не съм сигурен дали това ще се повтаря постоянно)