Saya memiliki situasi di mana saya mendapatkan kebuntuan, dan saya pikir saya telah mempersempit pelakunya, tetapi saya tidak yakin apa yang bisa saya lakukan untuk memperbaikinya.
Ini pada lingkungan produksi yang menjalankan SQL Server 2008 R2.
Untuk memberi Anda pandangan yang sedikit disederhanakan tentang situasi:
Saya memiliki 3 tabel sebagaimana didefinisikan di bawah ini:
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
meja memiliki senyawa Primary Key didefinisikan sebagai member_id, activity_id
, karena saya hanya merasa perlu untuk mencari data pada tabel yang seperti itu.
Saya juga memiliki indeks nonclustered pada follow
:
CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes]
ON follow ( member_id ASC ) INCLUDE ( follower_id )
Selain itu, saya memiliki tampilan terikat-Skema network_activity
yang didefinisikan sebagai berikut:
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
Yang juga memiliki indeks pengelompokan unik:
CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id]
ON network_activity
(
member_id ASC,
activity_id ASC
)
Sekarang, saya memiliki dua prosedur tersimpan yang menemui jalan buntu. Mereka melalui proses berikut:
-- 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 (...)
Kedua prosedur ini berjalan dalam isolasi BACA KOMITMEN. Saya telah berhasil meng-query 1222 keluaran acara yang diperluas, dan telah menafsirkan yang berikut terkait dengan deadlock:
SP1 sedang menunggu
RangeS-S
kunci kunci padaIX_follow_member_id_includes
indeks sementara SP2 memegang kunci yang saling bertentangan (X)SP2 sedang menunggu
S
kunci mode menyalaPK_member_activity
sementara SP1 memegang kunci yang saling bertentangan (X)
Kebuntuan tampaknya terjadi pada baris terakhir dari setiap kueri (sisipan). Yang tidak jelas bagi saya adalah mengapa SP1 menginginkan kunci padaIX_follow-member_id_includes
indeks. Satu-satunya tautan, bagi saya, tampaknya berasal dari tampilan yang diindeks ini, itulah sebabnya saya memasukkannya.
Apa cara terbaik bagi saya untuk mencegah kebuntuan ini terjadi? Bantuan apa pun akan sangat dihargai. Saya tidak punya banyak pengalaman dalam menyelesaikan masalah kebuntuan.
Harap beri tahu saya jika ada informasi lain yang dapat saya berikan yang mungkin bisa membantu!
Terima kasih sebelumnya.
Sunting 1: Menambahkan beberapa informasi lebih per permintaan.
Berikut adalah output 1222 dari kebuntuan ini:
<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>
Pada kasus ini,
relatedObjectId 72057594098679808 sesuai dengan member_activity, PK_member_activity
relatedObjectId 72057594104905728 sesuai dengan follow, IX_follow_member_id_includes
Juga, berikut ini adalah gambaran yang lebih tepat tentang apa yang dilakukan SP1 dan 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)
juga 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)
Sunting 2: Setelah membaca ulang komentar, saya pikir saya akan menambahkan beberapa info tentang kolom apa yang merupakan kunci asing juga ...
member_activity.member_id
adalah kunci asing untuk sebuahmember
tabelmember_activity.activity_id
adalah kunci asing keactivity
mejafollow.member_id
adalah kunci asing untuk sebuahmember
tabelfollow.follower_id
adalah kunci asing untuk sebuahmember
tabel
Pembaruan 1:
Saya membuat beberapa perubahan yang saya pikir dapat membantu mencegah kebuntuan, tanpa hasil.
Perubahan yang saya lakukan adalah sebagai berikut:
-- 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)
;
dan dengan 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
Dengan dua perubahan ini, saya masih merasa kebuntuan.
Jika ada hal lain yang bisa saya berikan, harap beri tahu saya. Terima kasih.
sumber
SERIALIZABLE
(ada sedikit lebih dari itu, tapi ini adalah komentar bukan jawaban :)Jawaban:
Konflik bermuara pada
network_activity
menjadi Tampilan Terindeks yang perlu dipertahankan (secara internal) di seluruh pernyataan DML. Itu kemungkinan besar mengapa SP1 menginginkan kunci padaIX_follow-member_id_includes
indeks karena mungkin digunakan oleh View (tampaknya menjadi indeks penutup untuk View).Dua opsi yang mungkin:
Pertimbangkan untuk menjatuhkan Indeks Clustered pada Tampilan sehingga tidak lagi menjadi Tampilan Terindeks. Apakah manfaatnya melebihi biaya perawatan? Apakah Anda memilih dari itu cukup sering atau apakah perolehan kinerja karena diindeks layak? Jika Anda menjalankan procs ini lebih sering, maka mungkin biayanya lebih tinggi daripada manfaatnya?
Jika manfaat memiliki Tampilan diindeksasi lebih besar daripada biaya, maka pertimbangkan mengisolasi operasi DML terhadap tabel dasar dari Tampilan itu. Ini dapat dilakukan melalui penggunaan Kunci Aplikasi (lihat sp_getapplock dan sp_releaseapplock ). Aplikasi Kunci memungkinkan Anda membuat kunci di sekitar konsep sewenang-wenang. Artinya, Anda dapat mendefinisikan
@Resource
sebagai "network_activity" di kedua Procs Tersimpan Anda yang akan memaksa mereka untuk menunggu giliran. Setiap proc akan mengikuti struktur yang sama:Anda perlu mengelola kesalahan /
ROLLBACK
diri sendiri (sebagaimana dinyatakan dalam dokumentasi MSDN tertaut) jadi masukkan seperti biasaTRY...CATCH
. Tapi, ini memungkinkan Anda untuk mengelola situasi.Harap dicatat:
sp_getapplock
Sayasp_releaseapplock
harus menggunakan hemat; Kunci Aplikasi pasti bisa sangat berguna (seperti dalam kasus seperti ini) tetapi mereka hanya boleh digunakan ketika benar-benar diperlukan.sumber
member_id
menjadi@Resource
nilai. Itu tampaknya tidak berlaku untuk situasi khusus ini tetapi saya telah melihatnya digunakan seperti itu dan itu cukup berguna, terutama dalam sistem multi-penyewa di mana Anda ingin membatasi proses menjadi satu utas pada basis per pelanggan, tetapi masih memilikinya multithread di seluruh pelanggan.