Sadece dizinlenmiş görünümle ilişkili 2 tablodan kilitlenmeyi çözme


17

Kilitlenme yaşadığım bir durumum var ve sanırım suçluları daralttım, ama düzeltmek için ne yapabileceğimden tam olarak emin değilim.

Bu, SQL Server 2008 R2 çalıştıran bir üretim ortamındadır.

Size durumun biraz basitleştirilmiş bir görünümünü vermek için:


Aşağıda tanımlandığı gibi 3 tablo var:

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_activityTablo Birincil Anahtar olarak tanımlanan bir bileşik vardır member_id, activity_idsadece hiç o masaya yolda olduğunu verileri aramak gerekir, çünkü.

Ayrıca üzerinde kümelenmemiş bir dizin var follow:

CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes] 
ON follow ( member_id ASC ) INCLUDE ( follower_id )

Ayrıca, network_activityaşağıdaki gibi tanımlanan bir Şemaya bağlı görünüm var:

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

Ayrıca, benzersiz bir kümelenmiş dizin vardır:

CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id] 
ON network_activity
(
    member_id ASC,
    activity_id ASC
)

Şimdi, kilitlenmemiş iki saklı yordam var. Aşağıdaki süreçten geçerler:

-- 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 (...)

Bu 2 yordamın her ikisi de READ COMMITTED izolasyonunda çalışır. Ben 1222 genişletilmiş olaylar çıktı sorgulamayı başardım ve çıkmazlar ile ilgili olarak aşağıdaki yorum:

SP2 çakışan (X) bir kilidi tutarken SP1 dizinde bir RangeS-Stuş kilidi bekliyorIX_follow_member_id_includes

SP1 çakışan (X) bir kilidi tutarken SP2 bir Smod kilidini bekliyorPK_member_activity

Kilitlenmenin her sorgunun son satırında (ekler) gerçekleştiği görülüyor. Benim için belirsiz olan şey, SP1'in neden IX_follow-member_id_includesendeks üzerinde kilit istemesi . Bana göre tek bağlantı bu dizinli görünümden gibi görünüyor, bu yüzden dahil ettim.

Bu kilitlenmelerin olmasını önlememin en iyi yolu ne olurdu? Herhangi bir yardım çok takdir edilecektir. Kilitlenme sorunlarını çözme konusunda fazla deneyimim yok.

Yardımcı olabileceğim daha fazla bilgi varsa lütfen bize bildirin!

Şimdiden teşekkürler.


Düzenleme 1: İstek başına daha fazla bilgi ekleme.

İşte bu çıkmazdan 1222 çıktı:

<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>

Bu durumda,

relatedObjectId 72057594098679808 karşılık gelir member_activity, PK_member_activity

relatedObjectId 72057594104905728 karşılık gelir follow, IX_follow_member_id_includes

Ayrıca, SP1 ve SP2'nin ne yaptığına dair daha kesin bir resim

-- 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)

ayrıca 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)

Edit 2: Yorumları tekrar okuduktan sonra, hangi sütunların yabancı anahtarlar olduğu hakkında bazı bilgiler ekleyeceğimi düşündüm ...

  • member_activity.member_idbir membertablonun yabancı anahtarıdır
  • member_activity.activity_idactivitymasanın yabancı anahtarı
  • follow.member_idbir membertablonun yabancı anahtarıdır
  • follow.follower_idbir membertablonun yabancı anahtarıdır

Güncelleme 1:

Kilitlenmeyi önlemeye yardımcı olabileceğini düşündüğüm birkaç değişiklik yaptım, şanssız.

Yaptığım değişiklikler şöyle:

-- 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)
;

ve SP2 ile:

-- 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

Bu iki değişiklikle birlikte hala kilitlenmeler görüyorum.

Verebileceğim başka bir şey varsa lütfen bana bildirin. Teşekkürler.


taahhüt edilen okuma anahtar aralığı kilitleri almaz, sadece serileştirilebilir. Eğer kilitlenme aslında okundu gösteriyorsa gösterir (2), benim tahminlerim, kapakların altında serileştirilebilecek bir yabancı anahtarı değiştirmeye erişiyorsunuz (yine de okumayı taahhüt ettiniz). Dürüst olmak gerekirse daha fazla yardımcı olmak için tüm ddl ve sp's gerekir.
Sean, Sara Chipps'i kaldır '

@SeanGallardy, teşekkürler. Yanlış yorumlama ihtimaline karşı 1222 çıktısını içerecek şekilde düzenledim ve SP'lerin ne yaptığı hakkında daha fazla ayrıntı ekledim. Bu yardımcı olur mu?
Leland Richardson

2
@SeanGallardy Sorgu planının dizine alınmış görünümü koruyan kısmı dahili olarak çalışır SERIALIZABLE(bundan biraz daha fazlası vardır, ancak bu bir yanıt değildir :)
Paul White 9

@PaulWhite Anlayışınız için teşekkür ederim, bunu bilmiyordum! Hızlı bir test yaparak, saklı yordamlarınızda (RangeI-N, RangeS-S, RangeS-U) kesici uç sırasında serileştirilebilir kilitleme modlarını dizinlenmiş görünümle kesinlikle alabilirim. Kilitlenme sınırlarının içine düştüklerinde saklı yordamlarınızdaki ekler sırasında (örneğin aralık kilidinin tuttuğu alanda) birbirlerine karşı doğru zamanda vuran uyumsuz kilit modlarından çıkma olduğu görülüyor. Hem zamanlama hem de girdi veri çarpışmasını düşünürdüm.
Sean, Sara Chipps'i

Soru: SELECT ifadelerine bir HOLDLOCK ipucu ekleseydim, bu ekte kilidin oluşmasını önler mi?
Leland Richardson

Yanıtlar:


5

Çatışma network_activity, DML ifadelerinde (dahili olarak) korunması gereken bir Dizinlenmiş Görünüm olmakla sınırlıdır. Büyük olasılıkla SP1'in IX_follow-member_id_includesGörünüm tarafından büyük olasılıkla kullanıldığı için dizinde bir kilit istemesi budur (Görünüm için bir kaplama dizini gibi görünüyor).

İki olası seçenek:

  1. Kümelenmiş Dizini artık Dizine Alınmış Görünüm olmayacak şekilde Görünüm'e bırakmayı düşünün. Bakım maliyetinden ağır basmanın faydası var mı? Ondan yeterince sık mı seçiyorsunuz veya dizine eklemenin performans kazancı buna değer mi? Bu procs'ları oldukça sık çalıştırırsanız, maliyet belki faydadan daha yüksektir?

  2. Görünümün dizine eklenmesinin avantajı maliyetten daha yüksekse, DML işlemlerini bu Görünümün temel tablolarına göre izole etmeyi düşünün. Bu, Uygulama Kilitleri kullanılarak yapılabilir (bkz. Sp_getapplock ve sp_releaseapplock ). Uygulama Kilitleri, keyfi kavramlar etrafında kilitler oluşturmanıza olanak tanır. Anlamı,@Resource her iki Kayıtlı Prosesinizde de "network_activity" olarak bu da onları sıralarını beklemeye zorlar. Her proc aynı yapıyı izler:

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

    Hataları / ROLLBACKkendiniz (bağlantılı MSDN belgelerinde belirtildiği gibi) yönetmeniz gerekir TRY...CATCH. Ancak bu, durumu yönetmenize izin verir.
    Lütfen dikkat: sp_getapplock / sp_releaseapplockidareli kullanılmalıdır; Uygulama Kilitleri kesinlikle çok kullanışlı olabilir (bunun gibi durumlarda), ancak sadece kesinlikle gerekli olduğunda kullanılmalıdır.


Yardım için teşekkürler. Seçenek # 2 hakkında biraz daha okuyacağım ve bunun bizim için işe yarayıp yaramadığını göreceğim. Görünümü biraz okundu ve kümelenmiş dizin oldukça büyük bir yardım ... bu yüzden henüz kaldırmak istemem. Bunu denediğimde bir güncelleme geri geleceğim.
Leland Richardson

Bence sp_getapplock kullanmak işe yarayacak. Henüz üretim ortamımızda deneyemedim, ancak süresi dolmadan ödülün size verildiğinden emin olmak istedim. Çalıştığını onaylayabildiğimde burada güncelleme yapacağım!
Leland Richardson

Teşekkürler. Uygulama Kilitleri ile ilgili güzel bir şey member_id, @Resourcedeğer gibi bir şeyde birleştirmenin ayrıntı düzeyini değiştirebilmenizdir . Bu belirli bir durum için geçerli görünmüyor, ancak bunun böyle kullanıldığını gördüm ve özellikle süreci müşteri başına tek bir iş parçacığına sınırlamak istediğiniz çok kiracılı bir sistemde oldukça kullanışlı, ancak hala müşteriler arasında çok iş parçacığına sahip olmak.
Solomon Rutzky

Ben bir güncelleme vermek istedim ve bu demek ki bizim üretim ortamında çalışmaya karar verdik. :)
Leland Richardson
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.