У меня есть ситуация, когда у меня возникают тупики, и я думаю, что сузил преступников, но я не совсем уверен, что я могу сделать, чтобы это исправить.
Это в производственной среде под управлением SQL Server 2008 R2.
Чтобы дать вам несколько упрощенное представление о ситуации:
У меня есть 3 таблицы, как указано ниже:
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,
...
)
member_activity
Таблица имеет соединение Первичный ключ определяется как member_id, activity_id
, потому что я только когда - либо нужно искать данные в этой таблице таким образом.
У меня также есть некластеризованный индекс follow
:
CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes]
ON follow ( member_id ASC ) INCLUDE ( follower_id )
Кроме того, у меня есть привязанное к схеме представление, network_activity
которое определяется следующим образом:
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
Который также имеет уникальный кластерный индекс:
CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id]
ON network_activity
(
member_id ASC,
activity_id ASC
)
Теперь у меня есть две заблокированные хранимые процедуры. Они проходят следующий процесс:
-- 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 (...)
Эти 2 процедуры выполняются в режиме READ COMMITTED. Мне удалось запросить выходные данные расширенных событий 1222 и интерпретировать следующее в отношении взаимоблокировок:
SP1 ожидает
RangeS-S
блокировки ключа дляIX_follow_member_id_includes
индекса, в то время как SP2 содержит конфликтующую (X) блокировкуSP2 ожидает
S
блокировки режима,PK_member_activity
а SP1 держит конфликтующую (X) блокировку
Кажется, что тупик возникает в последней строке каждого запроса (вставки). Что мне неясно, так это то, почему SP1 хочет блокировки IX_follow-member_id_includes
индекса. Мне кажется, что единственная ссылка - это индексированное представление, поэтому я включил его.
Что было бы для меня лучшим способом предотвратить возникновение этих тупиков? Любая помощь приветствуется. У меня нет большого опыта в решении тупиковых ситуаций.
Пожалуйста, дайте мне знать, если есть какая-то дополнительная информация, которая может вам помочь!
Заранее спасибо.
Редактировать 1: Добавление дополнительной информации для каждого запроса.
Вот выход 1222 из этого тупика:
<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>
В таком случае,
relatedObjectId 72057594098679808 соответствует member_activity, PK_member_activity
relatedObjectId 72057594104905728 соответствует follow, IX_follow_member_id_includes
Кроме того, вот более точная картина того, что делают SP1 и 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)
также 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)
Изменить 2: После перечитывания комментариев, я подумал, что я хотел бы добавить некоторую информацию о том, что столбцы также являются внешними ключами ...
member_activity.member_id
это внешний ключ кmember
таблицеmember_activity.activity_id
это внешний ключ кactivity
таблицеfollow.member_id
это внешний ключ кmember
таблицеfollow.follower_id
это внешний ключ кmember
таблице
Обновление 1:
Я сделал пару изменений, которые, как я думал, могли бы помочь предотвратить тупик, но без удачи.
Я сделал следующие изменения:
-- 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)
;
и с 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
С этими двумя изменениями у меня все еще есть тупики.
Если есть что-то еще, что я могу предоставить, пожалуйста, дайте мне знать. Благодарю.
источник
SERIALIZABLE
(есть немного больше, чем это, но это комментарий, а не ответ :)Ответы:
Конфликт сводится к
network_activity
тому, чтобы быть индексированным представлением, которое необходимо поддерживать (внутренне) в выражениях DML. Вероятнее всего, именно поэтому SP1 хочет заблокироватьIX_follow-member_id_includes
индекс, поскольку он, вероятно, используется представлением (он выглядит как закрывающий индекс для представления).Два возможных варианта:
Попробуйте сбросить кластеризованный индекс в представлении, чтобы он больше не был индексированным представлением. Перевешивает ли польза от этого стоимость обслуживания? Вы выбираете из этого достаточно часто, или выигрыш в производительности при индексировании стоит того? Если вы запускаете эти процессы довольно часто, то, возможно, стоимость будет выше, чем выгода?
Если преимущество индексирования представления перевешивает затраты, то рассмотрите возможность изоляции операций DML и базовых таблиц этого представления. Это можно сделать с помощью блокировки приложений (см. Sp_getapplock и sp_releaseapplock ). Блокировки приложений позволяют создавать блокировки вокруг произвольных концепций. Это означает, что вы можете определить
@Resource
как «network_activity» в обоих ваших сохраненных процессах, что заставит их ждать своей очереди. Каждый процесс будет следовать той же структуре:Вам нужно
ROLLBACK
самим управлять ошибками (как указано в связанной документации MSDN), так что вставляйте в обычноеTRY...CATCH
. Но это позволяет вам управлять ситуацией.Обратите внимание:
sp_getapplock
/sp_releaseapplock
следует использовать с осторожностью; Блокировки приложений определенно могут быть очень удобными (например, в подобных случаях), но их следует использовать только в случае крайней необходимости.источник
member_id
в@Resource
стоимость. Похоже, что это не относится к данной конкретной ситуации, но я видел, что она используется таким образом, и это довольно удобно, особенно в мультитенантной системе, где вы хотите ограничить процесс одним потоком для каждого клиента, но все еще иметь многопоточность среди клиентов.