SQL Server'da, seçilen bir satır grubunun kilitli olup olmadığını kontrol etmenin bir yolu var mı?


21

Çok milyarlık bir satır tablosunda çok sayıda kaydı güncellemeye / silmeye çalışıyoruz. Bu popüler bir masa olduğundan, bu tablonun farklı bölümlerinde çok fazla aktivite var. Herhangi bir büyük güncelleme / silme etkinliği uzun süre boyunca engelleniyor (tüm satırlara veya sayfa kilidine veya tablo kilidine kilitlenmeyi bekliyor), zaman aşımına uğradı veya görevi tamamlamak için birkaç gün sürdü.

Bu nedenle, küçük satırları toplu halde silme yaklaşımını değiştiriyoruz. Ancak, seçilenlerin (100 veya 1000 veya 2000 satır diyelim) şu anda farklı bir işlem tarafından kilitlenip kilitlenmediğini kontrol etmek istiyoruz.

  • Değilse, sil / güncelle ile devam edin.
  • Kilitlenirlerse, bir sonraki kayıt grubuna geçin.
  • Sonunda, başlangıç ​​noktasına geri dönün ve soldakileri güncellemeye / silmeye çalışın.

Bu yapılabilir mi?

Teşekkürler, ToC


2
Delete ifadesinin veya NOWAIT'in (grubun tamamında başarısız olması) bir parçası olarak READPAST'a baktınız mı? Bunlardan biri sizin için işe yarayabilir. msdn.microsoft.com/en-us/library/ms187373.aspx
Sean, Sara Chipps'i

@SeanGallardy Bu fikri göz önünde bulundurmadım, ama şimdi anlayacağım. Ancak, belirli bir satırın kilitli olup olmadığını kontrol etmenin daha kolay bir yolu var mı? Teşekkürler.
ToC

3
Ayrıca LOCK_TIMEOUT ( msdn.microsoft.com/en-us/library/ms189470.aspx ) konusuna da bakabilirsiniz . Örneğin, bu Adam Machanic'in sp_whoisactive'in bir yürütme planı toplamaya çalışırken engellenirse prosedürün çok uzun süre beklememesini sağlamasıdır. Kısa bir zaman aşımı süresi ayarlayabilir veya 0 değerini bile kullanabilirsiniz ("0 hiç beklememek ve bir kilitle karşılaşılır karşılaşmaz mesajı geri almak anlamına gelir.") Bunu 1222 hatasını yakalamak için bir TRY / CATCH ile birleştirebilirsiniz () "Kilit isteği zaman aşımı süresi aşıldı") ve bir sonraki partiye geçin.
Geoff Patterson

@gpatterson İlginç yaklaşım. Bunu da deneyeceğim.
ToC

2
Cevap olarak, hayır, uygulamada özel olarak yapılmış bir şey olmadıkça, sıraların kilitlenip kilitlenmediğini görmek için kolay bir yol yoktur. Temel olarak, önce lock_timeout setiyle HOLDLOCK ve XLOCK ile bir seçim yapabilirsiniz (bu, orijinal yorumumda NOWAIT'in ne olduğu, zaman aşımını 0'a ayarlayarak). Anlamazsan, bir şeyin kilitli olduğunu biliyorsun. Hiçbir şey kolay "Dizin Z şey tarafından kilitlenmiş kullanarak Tablo Y'de satır X mı" demek için kullanılabilir. Tablonun kilitleri olup olmadığını veya herhangi bir sayfa / satır / anahtar / etc'nin kilitleri olup olmadığını görebiliriz, ancak bunu bir sorguda belirli satırlara çevirmek kolay olmaz.
Sean,

Yanıtlar:


10

İsteği doğru anlarsam, amaç satır sıralarını silerken, aynı zamanda DML işlemleri tablodaki satırlarda gerçekleşir. Amaç bir toplu işlemi silmek; Bununla birlikte, söz konusu parti tarafından tanımlanan aralık içinde yer alan herhangi bir satır kilitliyse, o grubu atlamalı ve bir sonraki gruba geçmeliyiz. Daha önce silinmemiş herhangi bir gruba geri dönmeli ve orijinal silme mantığımızı yeniden denemeliyiz. Tüm gerekli satır grupları silinene kadar bu döngüyü tekrarlamalıyız.

Belirtildiği gibi, engellenen satırları içerebilecek geçmiş aralıkları atlamak için bir READPAST ipucu ve READ COMMITTED (varsayılan) yalıtım seviyesinin kullanılması makul olacaktır. Bir adım daha ileri gidip SERIALIZABLE izolasyon seviyesi ve nibbling silmelerini kullanmanızı öneririm.

SQL Server, seri hale getirilebilir işlem yalıtım düzeyini kullanırken Transact-SQL deyimi tarafından okunan bir kayıt kümesinde örtülü olarak bulunan bir dizi satırı korumak için Anahtar Aralığı kilitlerini kullanır ... daha fazlasını burada bulabilirsiniz: https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx

Nibbling silme ile hedefimiz, bir dizi satırı izole etmek ve bunları silerken bu satırlarda hiçbir değişiklik olmayacağından emin olmaktır, yani, hayalet okumalar veya eklemeler istemiyoruz. Serileştirilebilir izolasyon seviyesi bu sorunu çözmeyi amaçlamaktadır.

Çözümümü göstermeden önce, veritabanınızın varsayılan yalıtım düzeyini SERIALIZABLE olarak değiştirmeyi önermediğimi ya da çözümümün en iyisi olmasını önermiyorum. Sadece onu sunmak ve buradan nereye gidebileceğimizi görmek istiyorum.

Birkaç ev tutma notu:

  1. Kullandığım SQL Server sürümü Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
  2. Test veritabanım TAM kurtarma modelini kullanıyor

Denememe başlamak için, bir test veritabanı ve örnek bir tablo oluşturacağım ve tabloyu 2.000.000 satırla dolduracağım.


USE [master];
GO

SET NOCOUNT ON;

IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
    ALTER DATABASE [test] SET SINGLE_USER
        WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [test];
END
GO

-- Create the test database
CREATE DATABASE [test];
GO

-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;

-- Create a FULL database backup
-- in order to ensure we are in fact using 
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO

USE [test];
GO

-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
    DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
      c1 BIGINT IDENTITY (1,1) NOT NULL
    , c2 INT NOT NULL
) ON [PRIMARY];
GO

-- Insert 2,000,000 rows 
INSERT INTO dbo.tbl
    SELECT TOP 2000
        number
    FROM
        master..spt_values
    ORDER BY 
        number
GO 1000

Bu noktada, SERİLEŞTİRilebilir yalıtım seviyesinin kilitleme mekanizmalarının üzerinde etkili olabileceği bir veya daha fazla endekse ihtiyacımız olacak.


-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
    ON dbo.tbl (c1);
GO

-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2 
    ON dbo.tbl (c2);
GO

Şimdi 2.000.000 satırımızın oluşturulduğunu kontrol edelim.


SELECT
    COUNT(*)
FROM
    tbl;

görüntü tanımını buraya girin

Böylece, veritabanımıza, tablomuza, indekslere ve satırlara sahibiz. Öyleyse, nibbling silmeleri için deney hazırlayalım. İlk önce, tipik bir nibbling silme mekanizması oluşturmanın en iyi yoluna karar vermeliyiz.


DECLARE
      @BatchSize        INT    = 100
    , @LowestValue      BIGINT = 20000
    , @HighestValue     BIGINT = 20010
    , @DeletedRowsCount BIGINT = 0
    , @RowCount         BIGINT = 1;

SET NOCOUNT ON;
GO

WHILE  @DeletedRowsCount <  ( @HighestValue - @LowestValue ) 
BEGIN

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION

        DELETE 
        FROM
            dbo.tbl 
        WHERE
            c1 IN ( 
                    SELECT TOP (@BatchSize)
                        c1
                    FROM
                        dbo.tbl 
                    WHERE 
                        c1 BETWEEN @LowestValue AND @HighestValue
                    ORDER BY 
                        c1
                  );

        SET @RowCount = ROWCOUNT_BIG();

    COMMIT TRANSACTION;

    SET @DeletedRowsCount += @RowCount;
    WAITFOR DELAY '000:00:00.025';
    CHECKPOINT;

END;

Gördüğünüz gibi, açık işlemi while döngüsünün içine yerleştirdim. Günlük sifonlarını sınırlamak istiyorsanız, o zaman döngünün dışına yerleştirmekten çekinmeyin. Ayrıca, TAM kurtarma modelinde olduğumuz için, işlem günlüğünüzün aşırı derecede büyümesinin önlenebilmesini sağlamak için nibbling silme işlemlerinizi gerçekleştirirken işlem günlüğü yedeklemelerini daha sık oluşturmak isteyebilirsiniz.

Yani, bu kurulum ile birkaç hedefim var. İlk olarak, anahtar aralık kilitlerimi istiyorum; bu yüzden partileri mümkün olduğunca küçük tutmaya çalışıyorum. Ayrıca "devasa" masamdaki eşzamanlılığı olumsuz etkilemek de istemiyorum; bu yüzden kilitlerimi alıp olabildiğince hızlı bırakmak istiyorum. Bu yüzden parti büyüklüğünüzü küçük kılmanızı öneririm.

Şimdi, bu silme rutininin çok kısa bir örneğini işlem yapmak istiyorum. SSMS'de yeni bir pencere açmalı ve tablomuzdan bir satır silmeliyiz. Bunu, varsayılan READ COMMITTED izolasyon seviyesini kullanarak gizli bir işlem içinde yapacağım.


DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;

Bu satır gerçekten silindi mi?


SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20010;

Evet silindi.

Silinen Satırın Kanıtı

Şimdi, kilitlerimizi görmek için, SSMS içinde yeni bir pencere açalım ve bir veya iki kod kodu ekleyelim. Burada bulunan Adam Mechanic sp_whoisactive kullanıyorum: sp_whoisactive


SELECT
    DB_NAME(resource_database_id) AS DatabaseName
  , resource_type
  , request_mode
FROM
    sys.dm_tran_locks
WHERE
    DB_NAME(resource_database_id) = 'test'
    AND resource_type = 'KEY'
ORDER BY
    request_mode;

-- Our insert
sp_lock 55;

-- Our deletions
sp_lock 52;

-- Our active sessions
sp_whoisactive;

Şimdi başlamaya hazırız. Yeni bir SSMS penceresinde, sildiğimiz bir satırı tekrar yerleştirmeye çalışacak açık bir işlem başlayalım. Aynı zamanda nibbling silme işlemimizi de başlatacağız.

İnsert kodu:


BEGIN TRANSACTION

    SET IDENTITY_INSERT dbo.tbl ON;

    INSERT  INTO dbo.tbl
            ( c1 , c2 )
    VALUES
            ( 20005 , 1 );

    SET IDENTITY_INSERT dbo.tbl OFF;

--COMMIT TRANSACTION;

Her iki işlemi de eklenti ile başlayıp silme işlemlerimizle başlatalım. Anahtar aralıklı kilitleri ve özel kilitleri görebiliriz.

Menzil ve Özel Kilitler

Ek bu kilitleri oluşturdu:

Ekle Kilitleri

Nibbling delete / select bu kilitleri tutuyor:

görüntü tanımını buraya girin

Ekimiz, silinmemizi beklendiği gibi engelliyor:

Blok Ekle Sil

Şimdi ekleme işlemini yapalım ve ne olduğunu görelim.

Silme işlemini tamamla

Beklenildiği gibi tüm işlemler tamamlandı. Şimdi, ekin bir hayalet olup olmadığını veya silme işleminin de kaldırılıp kaldırılmadığını kontrol etmeliyiz.


SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20015;

Aslında, ek silindi; bu yüzden, hiçbir hayalet eke izin verilmedi.

Fantom Eklemesi Yok

Dolayısıyla, sonuç olarak, bu alıştırmanın asıl amacının her satır, sayfa veya tablo düzeyinde kilitlemeyi denemek ve izlememek ve bir toplu iş öğesinin kilitli olup olmadığını belirlemeye çalışmak ve bu nedenle silme işlemimizin yapılmasını gerektireceğini düşünüyorum. Bekleyin. Bu, sorgulayıcıların amacı olabilirdi; Ancak, bu görev herculean ve imkansız değilse de temelde pratik değildir. Asıl amaç, parti yelpazemizi kendi kilitlerimizle izole ettikten ve daha sonra parti silme işleminden önce bir fenomenin ortaya çıkmamasını sağlamaktır. SERİLEŞTİRİLEN izolasyon seviyesi bu hedefe ulaşır. Kilit nokta, küçük uçlarınızı küçük tutmak, işlem günlüğünüzü kontrol altında tutmak ve istenmeyen olayları ortadan kaldırmaktır.

Hız istiyorsanız, o zaman bölümlendirilemeyen devasa derin masalar oluşturmayın ve bu nedenle en hızlı sonuç için bölüm değiştirme kullanamazsınız. Hızın anahtarı bölümleme ve paralelliktir; ıstırabın anahtarı çirkinlikler ve canlı kilitlemedir.

Lütfen ne düşündüğünü bilmeme izin ver.

SERİLEŞTİRİLEN yalıtım seviyesinin çalışmadaki bazı örneklerini oluşturdum. Aşağıdaki linklerde mevcut olmalıdırlar.

İşlemi Sil

İşlem ekle

Eşitlik Operasyonları - Gelecek Anahtar Değerlerde Anahtar Aralığı Kilitleri

Eşitlik Operasyonları - Singleton Mevcut Verilerin Alınması

Eşitlik Operasyonları - Singleton, Var Olmayan Verileri Getiriyor

Eşitsizlik İşlemleri - Menzil Anahtar Anahtar Kilitleri ve Sonraki Anahtar Değerler


9

Bu nedenle, küçük satırları toplu halde silme yaklaşımını değiştiriyoruz.

Bu küçük dikkatli gruplar veya parçalar halinde silmek için gerçekten iyi bir fikirdir . Ben ediyorum eklemek küçük waitfor delay '00:00:05'ise - ve veritabanı kurtarma modeline bağlı FULL, daha sonra yapmak log backupve eğer SIMPLEo zaman yapmak bir manual CHECKPOINTişlem günlüğünün şişkinlik önlemek için - yığınlar arasında.

Ancak, seçilenlerin (100 veya 1000 veya 2000 satır diyelim) şu anda farklı bir işlem tarafından kilitlenip kilitlenmediğini kontrol etmek istiyoruz.

Söyledikleriniz tamamen kutunun dışında mümkün değil (3 mermi noktanızı göz önünde bulundurarak). Yukarıdaki öneri - small batches + waitfor delayişe yaramazsa (uygun sınama yapıyorsanız), o zaman kullanabilirsiniz query HINT.

Kullanmayın NOLOCK- bkz kb / 308886 , Itzik Ben-Gan tarafından SQL Server Okuma-Tutarlılık Problemleri , By Aaron Bertrand - her yerde nolock koymak ve SQL Server nolock Öneri & diğer kötü fikirler .

READPASTipucu senaryonuza yardımcı olacaktır. İpucunun özü READPASTşudur: bir satır seviyesi kilidi varsa, SQL sunucusu bunu okumaz.

Veritabanı Altyapısının, diğer işlemler tarafından kilitlenmiş satırları okumadığını belirtir. Ne zaman READPASTbelirtildiğinde, satır düzeyinde kilit atlanır. Diğer bir deyişle, Veritabanı Altyapısı, kilitleri serbest bırakılana kadar geçerli işlemi engellemek yerine satırları atlar.

Kullanırken Benim sınırlı Testler sırasında, ben gerçekten iyi verim bulundu DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)ve sorgu oturumu yalıtım düzeyini ayarlama READ COMMITTEDkullanarak SET TRANSACTION ISOLATION LEVEL READ COMMITTEDvarsayılan yalıtım düzeyi zaten olan.


2

Soruyla ilgili olarak yorumlarda ilk olarak sunulan diğer yaklaşımların özetlenmesi .


  1. NOWAITİstenilen davranış uyumsuz bir kilitle karşılaşır karşılaşmaz tüm öbeğin başarısız olması durumunda kullanın .

    Gönderen NOWAITbelgeler :

    Veritabanı Altyapısına, masaya kilitlendiği anda bir mesaj göndermesini söyler. belirli bir tablo NOWAITiçin belirtmeye eşdeğerdir SET LOCK_TIMEOUT 0. NOWAITNe zaman ipucu çalışmıyor TABLOCKipucu da dahildir. Bir TABLOCKipucunu kullanırken beklemeden bir sorguyu sonlandırmak için sorguyu SETLOCK_TIMEOUT 0;bunun yerine kullanın.

  2. SET LOCK_TIMEOUTBenzer bir sonuç elde etmek için ancak yapılandırılabilir bir zaman aşımı elde etmek için kullanın :

    Gönderen SET LOCK_TIMEOUTdokümantasyon

    Bir ifadenin kilidin serbest bırakılmasını beklediği milisaniye sayısını belirtir.

    Bir kilit için bekleme zaman aşımı değerini aştığında, bir hata döndürülür. 0 değeri, hiç beklememek ve bir kilitle karşılaşılır karşılaşmaz bir mesaj göndermek anlamına gelir.


0

2 paralellik sorgumuz olduğunu varsayalım:

connect / session 1: satırı kilitleyecektir = 777

SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777

connect / session 2: kilitli satırı görmezden gelecek = 777

SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777

VEYA bağlan / oturum 2: istisna atar

DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;

-1

Böyle bir şeyi filtrelemeyi deneyin - gerçekten, gerçekten belirgin olmak istiyorsanız karmaşık bir hale gelebilir . Sys.dm_tran_locks açıklaması için BOL'a bakın

SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id

sadece meraklı - neden olumsuz?
rottengeek 11:15

-11

Silme sırasında NoLOCK kullanabilirsiniz ve satırlar kilitliyse silinmezler. Bu ideal değil ama sizin için hile yapabilirsiniz.

DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True

7
Bunu yerel makinemde denersemMsg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements. , 2005'ten beri itirazım var
Tom V - Team Monica
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.