Czy w Sql Server można sprawdzić, czy wybrana grupa wierszy jest zablokowana, czy nie?

21

Próbujemy zaktualizować / usunąć dużą liczbę rekordów w wielomiliardowej tabeli wierszy. Ponieważ jest to popularny stół, w różnych jego częściach jest dużo aktywności. Wszelkie duże działania związane z aktualizacją / usuwaniem są blokowane przez dłuższy czas (ponieważ czeka na uzyskanie blokad wszystkich wierszy lub blokad strony lub tabeli), co powoduje przekroczenie limitu czasu lub wykonanie wielu dni.

Tak więc zmieniamy podejście do usuwania małych partii wierszy jednocześnie. Ale chcemy sprawdzić, czy wybrane (powiedzmy 100 lub 1000 lub 2000 wierszy) są obecnie zablokowane przez inny proces, czy nie.

  • Jeśli nie, kontynuuj usuwanie / aktualizację.
  • Jeśli są zablokowane, przejdź do następnej grupy rekordów.
  • Na koniec wróć na początek i spróbuj zaktualizować / usunąć pominięte.

Czy to jest wykonalne?

Dzięki, ToC

ToC
źródło
2
Czy przeglądałeś READPAST jako część instrukcji usuwania lub NOWAIT (aby zawieść całą grupę)? Jeden z nich może Ci pomóc. msdn.microsoft.com/en-us/library/ms187373.aspx
Sean mówi Usuń Sara Chipps
@SeanGallardy Nie rozważyłem tego pomysłu, ale teraz to zrobię. Ale czy istnieje łatwiejszy sposób sprawdzenia, czy dany wiersz jest zablokowany, czy nie? Dzięki.
ToC
3
Możesz także zajrzeć do LOCK_TIMEOUT ( msdn.microsoft.com/en-us/library/ms189470.aspx ). Na przykład w ten sposób sp_whoisactive Adama Machanica zapewnia, że ​​procedura nie będzie czekać zbyt długo, jeśli zostanie zablokowana podczas próby zebrania planu wykonania. Możesz ustawić krótki limit czasu lub nawet użyć wartości 0 („0 oznacza w ogóle nie czekać i zwrócić komunikat, gdy tylko napotkamy blokadę.”) Możesz połączyć to z TRY / CATCH, aby złapać błąd 1222 ( „Przekroczono limit czasu żądania blokady”) i przejdź do następnej partii.
Geoff Patterson
@gpatterson Ciekawe podejście. Spróbuję też tego.
ToC
2
Aby odpowiedzieć, nie, nie ma łatwiejszego sposobu, aby sprawdzić, czy wiersze są zablokowane, chyba że w aplikacji jest coś konkretnie zrobionego. Zasadniczo możesz najpierw dokonać wyboru za pomocą HOLDLOCK i XLOCK z zestawem lock_timeout (o tym właśnie jest NOWAIT w moim oryginalnym komentarzu, ustawiając limit czasu na 0). Jeśli tego nie rozumiesz, to wiesz, że coś jest zablokowane. Nic nie jest łatwo powiedzieć „Czy wiersz X w tabeli Y używa indeksu Z zablokowanego przez coś”. Możemy zobaczyć, czy tabela ma blokady, czy też strony / wiersze / klucze / etc mają blokady, ale przetłumaczenie tego na określone wiersze w zapytaniu nie byłoby łatwe.
Sean mówi Usuń Sara Chipps

Odpowiedzi:

10

Jeśli dobrze rozumiem żądanie, celem jest usunięcie partii wierszy, a jednocześnie operacje DML występują w wierszach w całej tabeli. Celem jest usunięcie partii; jeśli jednak wszystkie leżące poniżej wiersze mieszczące się w zakresie określonym przez tę partię są zablokowane, musimy pominąć tę partię i przejść do następnej partii. Następnie musimy powrócić do partii, które nie zostały wcześniej usunięte, i ponowić próbę naszej oryginalnej logiki usuwania. Musimy powtarzać ten cykl, aż wszystkie wymagane partie wierszy zostaną usunięte.

Jak już wspomniano, uzasadnione jest użycie podpowiedzi PRZECZYTAJ WSTĘP i poziomu izolacji PRZECZYTAJ ZAANGAŻOWANY (domyślny), aby pominąć zakresy przeszłości, które mogą zawierać zablokowane wiersze. Pójdę o krok dalej i zalecę użycie SERIALIZABLE poziomu izolacji i skubanie.

SQL Server używa blokad zakresu klucza do ochrony zakresu wierszy domyślnie zawartych w zestawie rekordów odczytywanym przez instrukcję Transact-SQL podczas korzystania z poziomu izolacji transakcji możliwego do serializacji ... więcej informacji tutaj: https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx

W przypadku usuwania skubania naszym celem jest wyodrębnienie zakresu wierszy i dopilnowanie, aby żadne wiersze nie wystąpiły podczas ich usuwania, to znaczy, nie chcemy odczytywania fantomów ani wstawiania. Szeregowy poziom izolacji ma rozwiązać ten problem.

Zanim zademonstruję swoje rozwiązanie, chciałbym dodać, że ani nie zalecam przełączania domyślnego poziomu izolacji bazy danych na SERIALIZABLE, ani nie zalecam, aby moje rozwiązanie było najlepsze. Chcę tylko to przedstawić i przekonać się, dokąd możemy się udać.

Kilka notatek dotyczących utrzymania domu:

  1. Używana przeze mnie wersja SQL Server to Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
  2. Moja testowa baza danych używa modelu odzyskiwania FULL

Aby rozpocząć eksperyment, utworzę testową bazę danych, przykładową tabelę i wypełnię tabelę 2 000 000 wierszy.


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

W tym momencie będziemy potrzebować jednego lub więcej indeksów, na które mogą działać mechanizmy blokujące SERIALIZABLE poziom izolacji.


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

Teraz sprawdźmy, czy utworzono nasze 2 000 000 wierszy


SELECT
    COUNT(*)
FROM
    tbl;

wprowadź opis zdjęcia tutaj

Mamy więc naszą bazę danych, tabelę, indeksy i wiersze. Ustawmy więc eksperyment usuwania skubań. Najpierw musimy zdecydować, jak najlepiej stworzyć typowy mechanizm usuwania skubania.


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;

Jak widać, umieściłem jawną transakcję w pętli while. Jeśli chcesz ograniczyć opróżnianie logów, umieść go poza pętlą. Ponadto, ponieważ jesteśmy w modelu odzyskiwania PEŁNYM, możesz częściej tworzyć kopie zapasowe dziennika transakcji podczas wykonywania operacji usuwania skubania, aby zapobiec nadmiernemu wzrostowi dziennika transakcji.

Tak więc mam kilka celów z tym ustawieniem. Najpierw chcę moje zamki z kluczem; więc staram się, aby partie były jak najmniejsze. Nie chcę również negatywnie wpływać na współbieżność na moim „gigantycznym” stole; więc chcę wziąć moje zamki i zostawić je tak szybko, jak to możliwe. Dlatego zalecam, aby twoje partie były małe.

Teraz chcę podać bardzo krótki przykład tej procedury usuwania. Musimy otworzyć nowe okno w SSMS i usunąć jeden wiersz z naszej tabeli. Zrobię to w ramach transakcji niejawnej przy użyciu domyślnego poziomu izolacji PRZECZYTAJ ZOBOWIĄZANIE.


DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;

Czy ten wiersz rzeczywiście został usunięty?


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

Tak, zostało usunięte.

Dowód usuniętego wiersza

Teraz, aby zobaczyć nasze zamki, otwórzmy nowe okno w SSMS i dodajmy fragment kodu lub dwa. Używam sp_whoisactive Adama Mechanica, który można znaleźć tutaj: 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;

Teraz jesteśmy gotowi do rozpoczęcia. W nowym oknie SSMS zacznijmy wyraźną transakcję, która spróbuje ponownie wstawić jeden wiersz, który usunęliśmy. W tym samym czasie uruchomimy naszą operację usuwania skubania.

Wstaw kod:


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;

Rozpocznijmy obie operacje, zaczynając od wstawki, a następnie usuwając. Widzimy zamki z kluczem i zamki ekskluzywne.

Zamki Range i eXclusive

Wstawka wygenerowała następujące blokady:

Zamki Insert

Nibbling delete / select trzyma te blokady:

wprowadź opis zdjęcia tutaj

Nasza wstawka blokuje nasze usuwanie zgodnie z oczekiwaniami:

Wstaw bloki Usuń

Teraz zatwierdź transakcję wstawiania i zobacz, co jest grane.

Zatwierdź usunięcie

I zgodnie z oczekiwaniami wszystkie transakcje zostały zakończone. Teraz musimy sprawdzić, czy wstawka była fantomem, czy też usunęła ją również operacja usuwania.


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

W rzeczywistości wstawka została usunięta; więc nie było dozwolone wstawianie fantomów.

Brak wkładki fantomowej

Podsumowując, uważam, że prawdziwym celem tego ćwiczenia nie jest próba śledzenia każdego wiersza, strony lub blokady na poziomie tabeli i próba ustalenia, czy element partii jest zablokowany, a zatem wymagałaby naszej operacji usuwania, aby czekać. Taka mogła być intencja pytających; zadanie to jest jednak herkulesowe i zasadniczo niepraktyczne, jeśli nie niemożliwe. Prawdziwym celem jest dopilnowanie, aby nie powstały żadne niepożądane zjawiska, gdy odizolujemy zakres naszej partii za pomocą własnych blokad, a następnie poprzedzimy usunięcie partii. SERIALIZABLE poziom izolacji osiąga ten cel. Kluczem do sukcesu jest utrzymywanie małych skubków, kontrolowanie dziennika transakcji i eliminowanie niepożądanych zjawisk.

Jeśli chcesz prędkości, nie buduj gigantycznie głębokich tabel, których nie można podzielić na partycje, a zatem nie można używać przełączania partycji w celu uzyskania najszybszych wyników. Kluczem do szybkości jest podział i równoległość; kluczem do cierpienia jest gryzienie i blokowanie życia.

Proszę daj mi znać co myślisz.

Stworzyłem kilka dalszych przykładów SERIALIZABLE poziomu izolacji w działaniu. Powinny być dostępne pod poniższymi linkami.

Usuń operację

Wstaw operację

Operacje na rzecz równości - blokady kluczowego zakresu przy kolejnych kluczowych wartościach

Operacje na rzecz równości - pobieranie istniejących danych w singletonie

Operacje na rzecz równości - pobieranie singleton nieistniejących danych

Nierówności Operacje - Blokowanie kluczowych zakresów przy zakresie i kolejnych kluczowych wartościach

ooutwire
źródło
9

Tak więc zmieniamy podejście do usuwania małych partii wierszy jednocześnie.

Jest to bardzo dobry pomysł, aby usunąć w małych partiach starannych lub kawałki . Chciałbym dodać mały waitfor delay '00:00:05'iw zależności od modelu odzyskiwania bazy danych - jeśli FULL, to zrobić log backupi jeśli SIMPLEwtedy zrobić manual CHECKPOINT, aby uniknąć wzdęcia z dziennika transakcji - pomiędzy partiami.

Ale chcemy sprawdzić, czy wybrane (powiedzmy 100 lub 1000 lub 2000 wierszy) są obecnie zablokowane przez inny proces, czy nie.

To, co mówisz, nie jest całkowicie możliwe od razu po wyjęciu z pudełka (pamiętając o 3 punktorach). Jeśli powyższa sugestia - small batches + waitfor delaynie działa (pod warunkiem, że wykonasz odpowiednie testy), możesz użyć query HINT.

Nie używaj NOLOCK- patrz kb / 308886 , Problemy dotyczące spójności odczytu serwera SQL autorstwa Itzika Ben-Gana , Umieszczanie NOLOCK wszędzie - autor Aaron Bertrand i SQL Server Wskazówka NOLOCK i inne słabe pomysły .

READPASTpodpowiedź pomoże w twoim scenariuszu. Istotą READPASTpodpowiedzi jest - jeśli istnieje blokada na poziomie wiersza, to serwer SQL jej nie przeczyta.

Określa, że ​​aparat bazy danych nie odczytuje wierszy zablokowanych przez inne transakcje. Gdy READPASTjest określony, blokady na poziomie wiersza są pomijane. Oznacza to, że aparat bazy danych przeskakuje obok wierszy zamiast blokować bieżącą transakcję do momentu zwolnienia blokad.

Podczas moich ograniczonych testów znalazłem naprawdę dobrą przepustowość podczas używania DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)i ustawiania poziomu izolacji sesji zapytania na READ COMMITTEDużywanie, SET TRANSACTION ISOLATION LEVEL READ COMMITTEDktóre jest domyślnym poziomem izolacji.

Kin Shah
źródło
2

Podsumowując inne podejścia pierwotnie oferowane w komentarzach do pytania.


  1. Użyj, NOWAITjeśli pożądane zachowanie ma zakończyć się niepowodzeniem całego fragmentu, gdy tylko napotkamy niezgodną blokadę.

    Z NOWAITdokumentacji :

    Instruuje aparat bazy danych, aby zwrócił komunikat, gdy tylko blokada napotka na stole. NOWAITjest równoważne z określeniem SET LOCK_TIMEOUT 0konkretnej tabeli. NOWAITWskazówka nie działa, gdy TABLOCKwskazówka jest również. Aby zakończyć kwerendę bez oczekiwania podczas korzystania ze TABLOCKpodpowiedzi, SETLOCK_TIMEOUT 0;zamiast tego wprowadź zapytanie .

  2. Użyj, SET LOCK_TIMEOUTaby osiągnąć podobny wynik, ale z konfigurowalnym limitem czasu:

    Z SET LOCK_TIMEOUTdokumentacji

    Określa liczbę milisekund, które instrukcja czeka na zwolnienie blokady.

    Gdy oczekiwanie na blokadę przekroczy wartość limitu czasu, zwracany jest błąd. Wartość 0 oznacza w ogóle nie czekać i zwracać komunikat, gdy tylko pojawi się blokada.

Paul White mówi GoFundMonica
źródło
0

Załóżmy, że mamy 2 zapytania równoległe:

connect / session 1: zablokuje wiersz = 777

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

connect / session 2: zignoruje zablokowany wiersz = 777

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

LUB połącz / sesja 2: wyrzuci wyjątek

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;
Lebnik
źródło
-1

Spróbuj przefiltrować coś takiego - może się to skomplikować, jeśli chcesz naprawdę, bardzo konkretnie. Poszukaj w BOL opisu sys.dm_tran_locks

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
rottengeek
źródło
po prostu ciekawy - dlaczego głosowanie negatywne?
rottengeek
-11

Możesz użyć NoLOCK podczas usuwania, a jeśli wiersze są zablokowane, nie zostaną usunięte. To nie jest idealne, ale może załatwić sprawę.

DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True
mouliin
źródło
7
Gdy próbuję że na moim komputerze lokalnym, które otrzymuję Msg 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., nieaktualnych od 2005
Tom V - Team Monica