Mengatasi kebuntuan dari 2 tabel hanya terkait melalui tampilan yang diindeks

17

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_activitymeja 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_activityyang 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-Skunci kunci padaIX_follow_member_id_includes indeks sementara SP2 memegang kunci yang saling bertentangan (X)

SP2 sedang menunggu Skunci mode menyala PK_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_idadalah kunci asing untuk sebuah membertabel
  • member_activity.activity_idadalah kunci asing ke activitymeja
  • follow.member_idadalah kunci asing untuk sebuah membertabel
  • follow.follower_idadalah kunci asing untuk sebuah membertabel

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.

Leland Richardson
sumber
read commit tidak mengambil kunci rentang kunci, hanya serializable yang melakukannya. Jika kebuntuan sebenarnya menunjukkan read berkomitmen (2) maka gues saya adalah Anda mengakses untuk mengubah kunci asing yang akan berubah menjadi serializable di bawah selimut (meskipun masih mengatakan read commit). Kami benar-benar membutuhkan seluruh ddl dan sp untuk membantu lebih lanjut.
Sean bilang Hapus Sara Chipps
@SeanGallardy, terima kasih. Saya telah diedit untuk memasukkan output 1222 jika saya menafsirkan salah, dan saya telah menambahkan rincian lebih lanjut tentang apa yang SP lakukan. Apakah ini membantu?
Leland Richardson
2
@SeanGallardy Bagian dari rencana kueri yang mempertahankan tampilan terindeks berjalan secara internal di SERIALIZABLE(ada sedikit lebih dari itu, tapi ini adalah komentar bukan jawaban :)
Paul White Reinstate Monica
@ PaulWhite Terima kasih atas wawasannya, saya tidak tahu itu! Melakukan tes cepat, saya pasti bisa mendapatkan mode penguncian serializable dengan tampilan terindeks selama memasukkan dalam prosedur tersimpan Anda (RangeI-N, RangeS-S, RangeS-U). Tampaknya seperti kebuntuan yang terjadi dari mode kunci yang tidak kompatibel memukul pada waktu yang tepat terhadap satu sama lain selama memasukkan dalam prosedur tersimpan Anda ketika mereka jatuh di dalam batas-batas kunci (misalnya di area yang dipegang oleh kunci jangkauan). Saya pikir waktu dan input data tabrakan.
Sean berkata Hapus Sara Chipps
Pertanyaan: Jika saya menambahkan petunjuk HOLDLOCK pada pernyataan SELECT, apakah itu akan mencegah kunci terjadi pada saat disisipkan?
Leland Richardson

Jawaban:

5

Konflik bermuara pada network_activitymenjadi Tampilan Terindeks yang perlu dipertahankan (secara internal) di seluruh pernyataan DML. Itu kemungkinan besar mengapa SP1 menginginkan kunci pada IX_follow-member_id_includesindeks karena mungkin digunakan oleh View (tampaknya menjadi indeks penutup untuk View).

Dua opsi yang mungkin:

  1. 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?

  2. 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 @Resourcesebagai "network_activity" di kedua Procs Tersimpan Anda yang akan memaksa mereka untuk menunggu giliran. Setiap proc akan mengikuti struktur yang sama:

    BEGIN TRANSACTION;
    EXEC sp_getapplock @Resource = 'network_activity', @LockMode = 'Exclusive';
    ...current proc code...
    EXEC sp_releaseapplock @Resource = 'network_activity';
    COMMIT TRANSACTION;

    Anda perlu mengelola kesalahan / ROLLBACKdiri sendiri (sebagaimana dinyatakan dalam dokumentasi MSDN tertaut) jadi masukkan seperti biasa TRY...CATCH. Tapi, ini memungkinkan Anda untuk mengelola situasi.
    Harap dicatat: sp_getapplock Saya sp_releaseapplockharus menggunakan hemat; Kunci Aplikasi pasti bisa sangat berguna (seperti dalam kasus seperti ini) tetapi mereka hanya boleh digunakan ketika benar-benar diperlukan.

Solomon Rutzky
sumber
Terima kasih untuk bantuannya. Saya akan membaca sedikit lebih banyak tentang opsi # 2 dan melihat apakah itu bekerja untuk kita. Tampilannya terbaca dari sedikit, dan indeks berkerumun adalah bantuan yang cukup besar ... jadi saya lebih suka belum menghapusnya. Saya akan kembali pembaruan setelah saya mencoba ini.
Leland Richardson
Saya pikir menggunakan sp_getapplock akan berhasil. Saya belum bisa mencobanya di lingkungan produksi kami, tapi saya ingin memastikan Anda mendapat hadiah sebelum berakhir. Saya akan memperbarui di sini ketika saya dapat mengkonfirmasi itu berfungsi!
Leland Richardson
Terima kasih. Satu hal yang menyenangkan tentang Application Locks adalah Anda dapat mengubah tingkat granularity dari menyatukan dalam sesuatu seperti member_idmenjadi @Resourcenilai. 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.
Solomon Rutzky
Saya ingin memberikan update dan mengatakan bahwa ini tidak berakhir bekerja di lingkungan produksi kami. :)
Leland Richardson