Mam sytuację, w której dostaję impasu i myślę, że zawęziłem sprawców, ale nie jestem pewien, co mogę zrobić, aby to naprawić.
Jest to w środowisku produkcyjnym z programem SQL Server 2008 R2.
Aby nieco uprościć widok sytuacji:
Mam 3 tabele zdefiniowane poniżej:
TABLE activity (
id, -- PK
...
)
TABLE member_activity (
member_id, -- PK col 1
activity_id, -- PK col 2
...
)
TABLE follow (
id, -- PK
follower_id,
member_id,
...
)
The member_activity
Tabela ma klucz podstawowy związek zdefiniowany jak member_id, activity_id
, bo ja zawsze tylko trzeba spojrzeć w górę dane dotyczące tej tabeli tej drodze.
Mam również indeks nieklastrowany follow
:
CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes]
ON follow ( member_id ASC ) INCLUDE ( follower_id )
Dodatkowo mam widok związany ze schematem network_activity
który jest zdefiniowany w następujący sposób:
CREATE VIEW network_activity
WITH SCHEMABINDING
AS
SELECT
follow.follower_id as member_id,
member_activity.activity_id as activity_id,
COUNT_BIG(*) AS cb
FROM member_activity
INNER JOIN follow ON follow.member_id = member_activity.member_id
INNER JOIN activity ON activity.id = member_activity.activity_id
GROUP BY follow.follower_id, member_activity.activity_id
Który ma również unikalny indeks klastrowany:
CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id]
ON network_activity
(
member_id ASC,
activity_id ASC
)
Teraz mam dwie zakleszczone procedury składowane. Przechodzą przez następujący proces:
-- SP1: insert activity
-----------------------
INSERT INTO activity (...)
SELECT ... FROM member_activity WHERE member_id = @a AND activity_id = @b
INSERT INTO member_activity (...)
-- SP2: insert follow
---------------------
SELECT follow WHERE member_id = @x AND follower_id = @y
INSERT INTO follow (...)
Te dwie procedury działają w izolacji PRZECZYTAJ ZOBOWIĄZANIE. Udało mi się przesłać zapytanie do 1222 danych wyjściowych zdarzeń rozszerzonych i zinterpretowałem następujące kwestie w odniesieniu do impasu:
Dodatek SP1 czeka na
RangeS-S
blokadę klucza wIX_follow_member_id_includes
indeksie, podczas gdy SP2 utrzymuje blokadę powodującą konflikt (X)Dodatek SP2 czeka na
S
włączenie blokady trybuPK_member_activity
Dodatek podczas gdy dodatek SP1 utrzymuje powodującą konflikt (X)
Wygląda na to, że impas występuje w ostatnim wierszu każdego zapytania (wstawek). Nie jest dla mnie jasne, dlaczego dodatek SP1 chce zablokowaćIX_follow-member_id_includes
indeks. Wydaje mi się, że jedynym linkiem jest ten indeksowany widok i dlatego go umieściłem.
Jaki byłby najlepszy sposób, aby zapobiec tym impasom? Każda pomoc będzie mile widziana. Nie mam dużego doświadczenia w rozwiązywaniu problemów z impasem.
Daj mi znać, jeśli mogę podać dodatkowe informacje, które mogą pomóc!
Z góry dziękuję.
Edycja 1: Dodanie dodatkowych informacji na żądanie.
Oto wyjście 1222 z tego impasu:
<deadlock>
<victim-list>
<victimProcess id="process4c6672748" />
</victim-list>
<process-list>
<process id="process4c6672748" taskpriority="0" logused="332" waitresource="KEY: 8:72057594104905728 (25014f77eaba)" waittime="581" ownerId="474698706" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.287" XDES="0x298487970" lockMode="RangeS-S" schedulerid="1" kpid="972" status="suspended" spid="79" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T10:25:00.283" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698706" currentdb="8" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="" line="7" stmtstart="1194" stmtend="1434" sqlhandle="0x02000000a26bb72a2b220406876cad09c22242e5265c82e6" />
<frame procname="" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000" />
</executionStack>
<inputbuf> <!-- SP 1 --> </inputbuf>
</process>
<process id="process6cddc5b88" taskpriority="0" logused="456" waitresource="KEY: 8:72057594098679808 (89013169fc76)" waittime="567" ownerId="474698698" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.283" XDES="0x30c459970" lockMode="S" schedulerid="4" kpid="4204" status="suspended" spid="70" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T15:04:55.870" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698698" currentdb="8" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="" line="18" stmtstart="942" stmtend="1250" sqlhandle="0x03000800ca458d315ee9130100a300000100000000000000" />
</executionStack>
<inputbuf> <!-- SP 2 --> </inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594104905728" dbid="8" objectname="" indexname="" id="lock33299fc00" mode="X" associatedObjectId="72057594104905728">
<owner-list>
<owner id="process6cddc5b88" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process4c6672748" mode="RangeS-S" requestType="wait" />
</waiter-list>
</keylock>
<keylock hobtid="72057594098679808" dbid="8" objectname="" indexname="" id="lockb7e2ba80" mode="X" associatedObjectId="72057594098679808">
<owner-list>
<owner id="process4c6672748" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process6cddc5b88" mode="S" requestType="wait" />
</waiter-list>
</keylock>
</resource-list>
</deadlock>
W tym przypadku,
relatedObjectId 72057594098679808 odpowiada member_activity, PK_member_activity
relatedObjectId 72057594104905728 odpowiada follow, IX_follow_member_id_includes
Ponadto, tutaj jest bardziej precyzyjny obraz tego, co robią SP1 i SP2
-- SP1: insert activity
-----------------------
DECLARE @activityId INT
INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)
SET @activityId = SCOPE_IDENTITY();
IF NOT EXISTS(
SELECT TOP 1 member_id
FROM member_activity
WHERE member_id = @m1 AND activity_id = @activityId
)
INSERT INTO member_activity (member_id, activity_id, field1)
VALUES (@m1, @activityId, @field1)
IF NOT EXISTS(
SELECT TOP 1 member_id
FROM member_activity
WHERE member_id = @m2 AND activity_id = @activityId
)
INSERT INTO member_activity (member_id, activity_id, field1)
VALUES (@m2, @activityId, @field1)
także SP2:
-- SP2: insert follow
---------------------
IF NOT EXISTS(
SELECT TOP 1 1
FROM follow
WHERE member_id = @memberId AND follower_id = @followerId
)
INSERT INTO follow (member_id, follower_id)
VALUES (@memberId, @followerId)
Edycja 2: Po ponownym przeczytaniu komentarzy pomyślałem, że dodam także informacje o tym, które kolumny są kluczami obcymi ...
member_activity.member_id
jest kluczem obcym domember
tabelimember_activity.activity_id
jest kluczem obcym doactivity
tabelifollow.member_id
jest kluczem obcym domember
tabelifollow.follower_id
jest kluczem obcym domember
tabeli
Aktualizacja 1:
Wprowadziłem kilka zmian, które moim zdaniem mogłyby pomóc w zapobieganiu impasu, bez powodzenia.
Wprowadzone przeze mnie zmiany były następujące:
-- SP1: insert activity
-----------------------
DECLARE @activityId INT
INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)
SET @activityId = SCOPE_IDENTITY();
MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m1 as member_id, @activityId as activity_id, @field1 as field1) as source
ON target.member_id = source.member_id
AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
INSERT (member_id, activity_id, field1)
VALUES (source.member_id, source.activity_id, source.field1)
;
MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m2 as member_id, @activityId as activity_id, @field1 as field1) as source
ON target.member_id = source.member_id
AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
INSERT (member_id, activity_id, field1)
VALUES (source.member_id, source.activity_id, source.field1)
;
oraz z dodatkiem SP2:
-- SP2: insert follow
---------------------
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
IF NOT EXISTS(
SELECT TOP 1 1
FROM follow WITH ( UPDLOCK )
WHERE member_id = @memberId AND follower_id = @followerId
)
INSERT INTO follow (member_id, follower_id)
VALUES (@memberId, @followerId)
COMMIT
Po tych dwóch zmianach wydaje mi się, że wciąż mam impas.
Jeśli mogę coś jeszcze podać, proszę dać mi znać. Dzięki.
źródło
SERIALIZABLE
(jest w tym nieco więcej, ale to komentarz, a nie odpowiedź :)Odpowiedzi:
Konflikt sprowadza się do tego,
network_activity
że jest widokiem indeksowanym, który należy zachować (wewnętrznie) w instrukcjach DML. Najprawdopodobniej SP1 chce zablokowaćIX_follow-member_id_includes
indeksu, ponieważ jest on prawdopodobnie używany przez widok (wygląda na to, że indeks obejmuje indeks).Dwie możliwe opcje:
Rozważ upuszczenie indeksu klastrowanego w widoku, aby nie był on już widokiem indeksowanym. Czy korzyść płynąca z posiadania go przewyższa koszty utrzymania? Czy wybierasz z niego wystarczająco często, czy warto zwiększyć wydajność indeksowania? Jeśli uruchamiasz te procesy dość często, to może koszt jest wyższy niż korzyść?
Jeśli korzyść z indeksowania Widoku przewyższa koszt, rozważ odizolowanie operacji DML od tabel podstawowych tego Widoku. Można tego dokonać za pomocą blokad aplikacji (patrz sp_getapplock i sp_releaseapplock ). Blokady aplikacji umożliwiają tworzenie blokad wokół dowolnych koncepcji. Oznacza to, że możesz zdefiniować
@Resource
„aktywność_sieciową” w obu przechowywanych procesach, co zmusi ich do oczekiwania na swoją kolej. Każdy proces miałby tę samą strukturę:Musisz samodzielnie zarządzać błędami
ROLLBACK
(jak podano w dołączonej dokumentacji MSDN), więc włącz toTRY...CATCH
. Pozwala to jednak zarządzać sytuacją.Uwaga:
sp_getapplock
/sp_releaseapplock
należy stosować oszczędnie; Blokady aplikacji mogą być bardzo przydatne (na przykład w takich przypadkach), ale należy ich używać tylko wtedy, gdy jest to absolutnie konieczne.źródło
member_id
do@Resource
wartości. Nie wydaje się, aby miało to zastosowanie do tej konkretnej sytuacji, ale widziałem, że jest tak używane i jest to całkiem przydatne, szczególnie w systemie z wieloma dzierżawcami, w którym chcesz ograniczyć proces do jednego wątku na klienta, ale wciąż jest wielowątkowy wśród klientów.