můžu mohl také reprodukovat 100% času na mém počítači. (viz poznámka na konci)
Podstatou problému je, že vyjímáte S
uzamkne řádky systémové tabulky v tempdb
které mohou být v konfliktu se zámky potřebnými pro interní tempdb
transakce čištění.
Když je toto čištění přiděleno stejné relaci, která vlastní S
lock může dojít k neurčitému zablokování.
Chcete-li se tomuto problému s jistotou vyhnout, musíte přestat odkazovat na system
objekty uvnitř tempdb
.
Je možné vytvořit číselnou tabulku bez odkazování na externí tabulky. Následující text nepotřebuje číst žádné řádky základní tabulky, a proto také nepoužívá žádné zámky.
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
Kroky pro reprodukci
Nejprve vytvořte proceduru
CREATE PROC P
AS
SET NOCOUNT ON;
DECLARE @T TABLE (X INT)
GO
Poté restartujte službu SQL a v jednom připojení proveďte
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
Pak v dalším připojení spustit
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
Zdá se, že dotazu vyplňujícímu tabulku Numbers se podařilo dostat do situace živého uzamčení s interními systémovými transakcemi, které čistí dočasné objekty, jako jsou proměnné tabulky.
Tímto způsobem se mi podařilo zablokovat ID relace 53. Je blokován na dobu neurčitou. Výstup sp_WhoIsActive
ukazuje, že tento spid tráví téměř všechen čas pozastaven. V po sobě jdoucích běhech čísla v reads
sloupec se zvyšuje, ale hodnoty v ostatních sloupcích zůstávají z velké části stejné.
Doba čekání nevykazuje rostoucí vzor, i když naznačuje, že musí být pravidelně odblokována, než bude znovu zablokována.
SELECT *
FROM sys.dm_os_waiting_tasks
WHERE session_id = blocking_session_id
Vrátí
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| 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 |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
Pomocí id v popisu zdroje
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
Vrátí
+------------+
| name |
+------------+
| sysschobjs |
+------------+
Běh
SELECT resource_description,request_status
FROM sys.dm_tran_locks
WHERE request_session_id = 53 AND request_status <> 'GRANT'
Vrátí
+----------------------+----------------+
| resource_description | request_status |
+----------------------+----------------+
| (246708db8c1f) | CONVERT |
+----------------------+----------------+
Připojení přes DAC a spuštění
SELECT id,name
FROM tempdb.sys.sysschobjs WITH (NOLOCK)
WHERE %%LOCKRES%% = '(246708db8c1f)'
Vrátí
+-------------+-----------+
| id | name |
+-------------+-----------+
| -1578606288 | #A1E86130 |
+-------------+-----------+
Zajímalo by mě, co to je
SELECT name,user_type_id
FROM tempdb.sys.columns
WHERE object_id = -1578606288
Vrátí
+------+--------------+
| name | user_type_id |
+------+--------------+
| X | 56 |
+------+--------------+
Toto je název sloupce v proměnné tabulky používané uloženým procesem.
Běh
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)'
Vrátí
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| 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 |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
Takže SELECT INTO
transakce drží S
uzamknout řádek v tempdb.sys.sysschobjs
týkající se proměnné tabulky #A1E86130
. droptemp
transakce nemůže dostat X
uzamknout tento řádek kvůli konfliktnímu S
zámek.
Opakovaným spuštěním tohoto dotazu zjistíte, že transaction_id
pro droptemp
transakce se opakovaně mění.
Domnívám se, že SQL Server musí tyto interní transakce přidělit uživatelským spidům a upřednostnit je před tím, než provede uživatelskou práci. Takže ID relace 53 uvízlo v konstantním cyklu, kdy spouští droptemp
transakce, je blokována uživatelskou transakcí běžící na stejném spid. Vrátí zpět interní transakci a poté proces neomezeně opakuje.
To je potvrzeno sledováním různých událostí zamykání a transakcí v SQL Server Profiler poté, co se spid zablokuje.
Také jsem sledoval události zamykání před tím.
Zamknout blokování událostí
Většina zámků se sdíleným klíčem odstraněna pomocí SELECT INTO
transakce na klíčích v sysschobjs
okamžitě propustit. Výjimkou je první zámek na (246708db8c1f)
.
To dává určitý smysl, protože plán ukazuje skenování vnořených smyček [sys].[sysschobjs].[clst] [o]
a protože dočasným objektům je přiděleno záporné ID objektu, budou to první řádky, na které narazíte v pořadí skenování.
Také jsem se setkal se situací popsanou v OP, kdy se zdá, že spuštění třícestného křížového spojení nejprve umožňuje úspěchu čtyřcestného.
Prvních několik událostí ve trasování pro SELECT INTO
transakce jsou zcela odlišné.
Stalo se to po restartu služby, takže hodnoty prostředků zámku ve sloupci textových dat nejsou přímo srovnatelné.
Zdá se, že namísto ponechání zámku na prvním klíči a následného vzoru získávání a uvolňování dalších klíčů získává mnohem více zámků, aniž by je zpočátku uvolňoval.
Předpokládám, že musí existovat určitá odchylka ve strategii provádění, která se problému vyhýbá.
Aktualizovat
Položka Connect, kterou jsem vytvořil o tom
nebyl označen jako opravený, ale nyní používám SQL Server 2012 SP2 a nyní mohu reprodukovat pouze dočasné samoblokování, nikoli trvalé. Stále mám samoblokování, ale po nějakém množství neúspěšných pokusů o provedení droptemp
transakce úspěšně, zdá se, že se vrací zpět ke zpracování uživatelské transakce. Poté se systémová transakce potvrdí a úspěšně se provede. Stále na stejném spidu. (proběhne osm pokusů v jednom příkladu. Nejsem si jistý, zda se to bude důsledně opakovat)