2009-05-08 20 views
5

Mam aplikację java, która wykonuje wiele równoczesnych operacji CRUD na bazie danych. Dodaję obsługę SQLServer, ale mam problemy z zakleszczeniem podczas jednoczesnego usuwania. Po pewnym dochodzeniu okazało się, że problem może wynikać z eskalacji blokady na konkretnej tabeli.Deadlock SQLServer

Próbując to naprawić, postanowiłem zrobić wszystkie odczyty na tabeli, o której mowa, "aktualizuj" za pomocą podpowiedzi UPDLOCK, aby uniknąć zakleszczenia. Jednak nadal widzę problem. Mam włączone śledzenie w SQLServer i znalazłem następujący ślad impasu w dziennikach SQLServer:

Deadlock napotkał .... Drukowanie informacji zakleszczenia poczekamy na wykresie

Węzeł: 1 KLUCZ: 5: 72057594042384384 (54048e7b3828) CleanCnt: Tryb 3: X Flagi: 0x0 Lista grantu 1: Właściciel: 0x03D08C40 Tryb: X Flg: 0x0 Ref: 0 Żywot: 02000000 SPID: 62 ECID: 0 XactLockInfo: 0x04834274 SPID: 62 ECID: 0 Instrukcja Wpisz: DELETE Numer linii: 1 Buf Wejściowy: Język Zdarzenie: (@ P0 nvarchar (4000)) usuń z part_data gdzie part_id = @ P0 Zlecenie: ResType: LockOwner stype: "OR'Xdes: 0x04B511C8 Tryb: u SPID 60 Identyfikator partii: 0 ECID 0 TaskProxy (0x058BE378) Wartość: 0x3d08500 koszt: (0/1296)

Węzeł 2

KLUCZ: 5: 72057594042384384 (f903d6d6e0ac) CleanCnt: 2 Tryb: X Flags: 0x0 Grant Lista 0: Właściciel: 0x03D088A0 Mode: X Flg: 0x0 Ref: 0 długość życia: 02000000 SPID: 60 ECID: 0 XactLockInfo: 0x04B511EC SPID: 60 ECID: 0 Typ instrukcji: DELETE Numer linii: 1 Buf wejściowy: Język Zdarzenie: (@ P0 nvarchar (4000)) usuń z part_data gdzie part_id = @ P0 Zlecenie: ResType: LockOwner Stype: 'OR'Xdes: 0x04834250 Tryb: U SPID: 62 BatchID: 0 ECID: 0 TaskProxy :(0x047BA378) Wartość: 0x3d089e0 Koszt: (0/4588)

Victim zasobów Właściciel: ResType: LockOwner stype: "OR'Xdes: Tryb 0x04B511C8: U SPID: 60 Identyfikator partii: 0 ECID: 0 TaskProxy: (0x058BE378) Wartość : 0x3d08500 Koszt: (0/1296)

Program profilujący SQLServer pokazuje to jako dwóch klientów posiadających blokady aktualizacji (U) i próbujących eskalować do wyłącznych blokad (X). Dokumenty SQLServer, które przeczytałem, mówią, że tylko jeden klient może mieć blokadę (U) na stole w danym czasie, więc zastanawiam się, dlaczego widzę sytuację pokazaną w śledzeniu.

Obiekt bazy danych, którego dotyczy odnośnik, jest indeksem klucza obcego. Jeśli ktoś, kto ma doświadczenie w naprawianiu tego rodzaju problemu, może udzielić porady, będzie to bardzo pomocne.

Dzięki, Brad.

EDIT dodany impas wykres xml jako wniosek:

<deadlock-list> 
<deadlock victim="process989018"> 
    <process-list> 
    <process id="process6aa7a8" taskpriority="0" logused="4844" waitresource="KEY: 5:72057594042384384 (5504bdfb7529)" waittime="9859" ownerId="613553" transactionname="implicit_transaction" lasttranstarted="2009-05-08T11:52:39.137" XDES="0x5fcbc30" lockMode="U" schedulerid="1" kpid="3516" status="suspended" spid="59" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2009-05-08T11:52:39.183" lastbatchcompleted="2009-05-08T11:52:39.183" clientapp="jTDS" hostname="LOIRE" hostpid="123" loginname="sa" isolationlevel="read committed (2)" xactid="613553" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058"> 
    <executionStack> 
    <frame procname="adhoc" line="1" stmtstart="40" sqlhandle="0x0200000007c76c39efdd8317c6fa7b611b4fd958f05cfcf4"> 
delete from part_data where part_id = @P0  </frame> 
    </executionStack> 
    <inputbuf>(@P0 nvarchar(4000))delete from part_data where part_id = @P0</inputbuf> 
    </process> 
    <process id="process989018" taskpriority="0" logused="1528" waitresource="KEY: 5:72057594042384384 (5e0405cb0377)" waittime="1250" ownerId="613558" transactionname="implicit_transaction" lasttranstarted="2009-05-08T11:52:39.183" XDES="0x48318f0" lockMode="U" schedulerid="2" kpid="2692" status="suspended" spid="60" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2009-05-08T11:52:39.183" lastbatchcompleted="2009-05-08T11:52:39.183" clientapp="jTDS" hostname="LOIRE" hostpid="123" loginname="sa" isolationlevel="read committed (2)" xactid="613558" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058"> 
    <executionStack> 
    <frame procname="adhoc" line="1" stmtstart="40" sqlhandle="0x0200000007c76c39efdd8317c6fa7b611b4fd958f05cfcf4"> 
delete from part_data where part_id = @P0  </frame> 
    </executionStack> 
    <inputbuf>(@P0 nvarchar(4000))delete from part_data where part_id = @P0</inputbuf> 
    </process> 
    </process-list> 
    <resource-list> 
    <keylock hobtid="72057594042384384" dbid="5" objectname="MESSAGESTOREDB61.dbo.part_data" indexname="idx_part_data_part_id" id="lock3cab740" mode="X" associatedObjectId="72057594042384384"> 
    <owner-list> 
    <owner id="process6aa7a8" mode="X"/> 
    </owner-list> 
    <waiter-list> 
    <waiter id="process989018" mode="U" requestType="wait"/> 
    </waiter-list> 
    </keylock> 
    <keylock hobtid="72057594042384384" dbid="5" objectname="MESSAGESTOREDB61.dbo.part_data" indexname="idx_part_data_part_id" id="lock3cad340" mode="X" associatedObjectId="72057594042384384"> 
    <owner-list> 
    <owner id="process989018" mode="X"/> 
    </owner-list> 
    <waiter-list> 
    <waiter id="process6aa7a8" mode="U" requestType="wait"/> 
    </waiter-list> 
    </keylock> 
    </resource-list> 
</deadlock> 
</deadlock-list> 
+0

Czy masz gdzieś jakieś kasowanie? –

+0

Witaj Stefan. Nie, nie mam żadnych ograniczeń kaskadowo-kasujących. – Brad

+0

format ekranu nie ma znaczenia, skopiuj/wklej do lokalnego pliku .XML i otwórz go i będzie ładnie wyglądał –

Odpowiedz

0

jestem przy założeniu, że zabrakło czegoś takiego: DBCC TRACEON (1222, -1) "i/lub" DBCC TRACEON (1204, -1) . Uważam, że ślad zakleszczenia w dziennikach SQLServer jest trudny do odczytania. Czy jesteś pewien, że jest to próba eskalacji do wyłącznych blokad (X)?

Spróbuj uruchomić śledzenie w narzędziu profilującym (wybierz pusty szablon), wybierz "Zdarzenie wykresu zakleszczenia", a na wyświetlonej nowej karcie (Ustawienia usuwania zdarzeń) zapisz każdy z nich (sprawdź "zapisuj zakleszczenia zdarzeń XML" osobno ") we własnym pliku.Otwórz ten plik w przeglądarce xml i łatwo będzie powiedzieć, co się dzieje. Każdy proces jest zawarty, ze stosem wykonawczym wywołań procedur/wywołań/itp. I wszystkie blokady są tam również. Trudno mi uwierzyć, że dwa błędy DELETE, w których col = @ powodują problem, spoglądają na stos wykonawczy, czy istnieje jakiś czynnik uruchamiający lub coś innego?

Przejrzyj sekcję "lista zasobów" pliku, pokaże, co jest blokowane i przechowywane przez każdy proces powodujący zakleszczenie. Dowiedz się, jak zablokować jeden z tych elementów i rozwiązać problem zakleszczenia.

+0

Cześć, dziękuję za komentowanie. Użyłem wcześniej profilera, ale teraz widzę, że interpretowałem niepoprawnie wynik. Z listy zasobów XML wynika, że ​​dwaj klienci mają już blokady (X) i faktycznie żądają (U) blokad. Ze stosu egzekucyjnego jedyne rzeczy, które pokazują, to dwie instrukcje usuwania. – Brad

+0

W jaki sposób można usuwać próby w każdym procesie? wielokrotny lub pojedynczy dla każdego procesu? Jeśli wykonujesz wiele operacji usuwania w pętli (z transakcją) w każdym procesie, możesz mieć problemy z blokowaniem, ponieważ blokady nie znajdują się w wierszach, ale na stronach z wieloma wierszami. edytuj swoje pytanie i dołącz wykres zakleszczenia xml, abym mógł lepiej zobaczyć, co widzisz. –

+0

Transakcja wykonuje usuwanie na wykresie obiektów, które łączą się w celu utworzenia logicznej jednostki danych. Tabela, w której usuwanie kończy się niepowodzeniem, odpowiada najbardziej położonemu obiektowi na tym wykresie. Jest prawdopodobne, że istnieje więcej niż jeden z tych obiektów, ale usuwanie jest wykonywane tylko raz na tym poziomie, ponieważ jest to "delete where foreign key =?". Wciąż jestem ciekawy, dlaczego impas dzieje się na indeksie, a nie na samym stole. – Brad

2

Zakleszczenia w SQLServer prawie zawsze pochodzą z faktu, że pojedynczy wątek próbuje pisać i czytać przy użyciu dwóch połączeń, a zatem dwóch transakcji. Jeśli chcesz, aby to działało, wykonaj wszystkie operacje w jednym wątku, używając JEDNEGO połączenia i upewnij się, że rzeczywiście korzystasz z połączenia ponownie. Może to być spowodowane warstwami w twojej aplikacji, że przypadkowo używasz innego połączenia do odczytywania podczas transakcji, co powoduje, że czytanie czeka na zakończenie drugiego kodu (przy użyciu innego połączenia), co nigdy się nie zdarza, ponieważ czytanie nigdy się nie kończy.

Przykład (kroki pseudo)

rozpocząć trans

  • zapisu danych (na przykład wprowadzenie, aktualizacja) do tabeli Foo
  • odczytywania niektórych danych (który powoduje przeszukiwanie tabeli) z Foo pomocą inny połączenie DEADLOCK, jak czytać nigdy się nie kończy, więc transakcja nigdy nie zostanie zatwierdzona
+0

Frans, to interesujący punkt. Komponent JDBC jest wykonywany za pomocą szablonu JDBC Spring, więc nie obsługuję żadnych połączeń samodzielnie, ale możliwe jest, że odczyt odbywa się wewnątrz transakcji obok aktualizacji lub usunięcia.Zamierzam wrócić i poszukać sytuacji, którą opisałeś – Brad

+0

Po dalszym dochodzeniu zauważyłem, że jedna z podejrzanych transakcji ma wstawioną mieszankę z usunięciami. Moja aplikacja używa czterech klas usług dla dostępu do bazy danych, które grupują operacje, jak czyta, aktualizuje, wstawia i usuwa. Domyślam się, że problem może polegać na tym, że wstawka używa różnych powiązań do usunięcia, ponieważ znajduje się w oddzielnej klasie szablonu JDBC. Myślałem, że zarządzanie transakcjami wiosennymi zmusi operacje do korzystania z jednego połączenia, być może się mylę? – Brad

+0

Nigdy nie pracowałem ze Springem, więc nie mam pojęcia, czy ponownie wykorzystuje transakcję w toku. Mogę sobie wyobrazić, że tak, ale też mogę sobie wyobrazić, że tak się nie stanie (jeśli na przykład połączysz się z inną bazą danych, MA BYĆ NOWYM) –

4

Witamy w okropnym.

Ostatni raz, kiedy wpadłem na taką sytuację, było spowodowane tym, że aktualizacja lub usunięcie nie było w stanie znaleźć dobrego indeksu, który pomógłby mu wyodrębnić wiersze, których dotyczył. Spowodowało to nieoczekiwaną eskalację blokady, ponieważ korzystało z niekryjącego indeksu do modyfikowania lokalizacji rekordów.

Więc jeśli możesz wyizolować niektóre zapytania, sprawdź dla nich sql i sprawdź, czy nie możesz poeksperymentować z dostarczaniem indeksów pokrywających.

Indeks zakrywający jest indeksem, który zawiera wszystkie pola w klauzuli where.

+0

Żaden indeks nie pomógłby, ale dobry indeks obejmuje wszystkie kolumny, które powinny zostać zwrócone. Jeśli wybierzesz cola, colb z tabeli 1 ... cola i colb powinny znajdować się w indeksie obejmującym, więc SQL Server nie musi powracać do pierwotnego wiersza, aby uzyskać dane. –

0

... postanowiłem zrobić wszystko czyta na stole w pytaniu być zrobione „do aktualizacji” pomocą wskazówkę UPDLOCK tak, że może się uniknąć impas ...

... Tak, istnieje transakcja zadeklarowana jako , aby cały wykres obiektu został usunięty lub pozostawiony w stanie nienaruszonym, jeśli wystąpi błąd . Nie ma żadnych innych baz operacje w transakcji, niektóre odczyty są wykonywane przed transakcją została rozpoczęta, lecz nikt w środku ...

Nie jestem pewien, czy rozumiem uzasadnienie eskalacji blokad odczytu poza transakcja, myślę, że to tylko pogorszyłoby problem. Z mojego doświadczenia wynika, że ​​podawanie wskazówek blokujących z wyprzedzeniem powoduje więcej szkód niż korzyści.

To powiedziawszy ...

Brzmi jak próbujesz usunąć wiele wierszy w obrębie każdej transakcji i że szachowanie występuje, ponieważ SQL eskalacji poziomu blokady na stole dla każdego. Pamiętaj, że w wierszu programu SQL Server Key-Range i Page locks wszystkie są natychmiast przenoszone do blokad tabel. Jeśli masz wiele transakcji, usuwając wiele wierszy, spróbują eskalacji, a dostaniesz zakleszczenia.

To zabije wydajność, jeśli masz wielu użytkowników, ale spróbuj podać wskazówki TABLOCK na swoim DELETE i sprawdzić, czy problem zniknie.

Przyjrzyj się także planom wykonania instrukcji SQL w celu sprawdzenia, w jaki sposób występują eskalacje blokady.

1

Po pierwsze nie używaj wskazówek, zazwyczaj lepiej jest pozostawić SQL Server na własną rękę.

Secondo sprawdzić, czy nie mają realną impasu (ale zdarza się znacznie rzadziej wyglądu impasu może schować).

Po trzecie, jak ktoś zasugerował, sprawdź, czy masz jakieś powolne zapytanie i ustaw je.

Z mojego doświadczenia każdym razem klient zgłosił sytuacji impasu Stało się to dlatego, że był powolny zapytanie running że eskaluje i nigdy prawdziwy impas i strojenia kwerendy lub dodanie konkretnego indeksu zawsze rozwiązało problem .

nie widzę, która wersja programu SQL Server mówisz, ale każda następna wersja ma lepsze zarządzanie impasu niż poprzedni i SQL Server 2000 był szczególnie przykre w tym temacie.

Pozdrowienia
Massimo

+0

Witaj Massimo, dziękuję za odpowiedź. Trochę zajęty, ale wrócę wkrótce i wrócę do ciebie. – Brad

0

Poznałem ten sam problem, wiele lat temu na stole kierowane przez ponad miliard transakcji dziennie.

Mieliśmy cztery usługi tomcat do obsługi zapytań na jednej stronie. Czasami dwie różne usługi próbowały potraktować to samo zapytanie, które pojawia się, gdy ruch w witrynie był najniższy. Operacja usuwania pierwszego zapytania blokowała IX, a drugie kwerenda, wykonana w tym samym czasie, robiła to samo.

Rozwiązaliśmy ten problem, tworząc tabelę buforów. Akcje usuwania zostały zapisane w tej tabeli. Co sekundę zadanie sql odczytuje tę tabelę, aby usunąć linie oznaczone jako "do usunięcia".

0

Przede wszystkim są to dwie instrukcje usuwania. Te dwa SPID realizują tę samą deklarację

delete from part_data where part_id = @P0

i prawdopodobnie przyzwyczajenie się próby usunięcia tego samego rekordu. Tutaj dochodzi do zakleszczenia, ponieważ SQLServer nie może zidentyfikować wiersza/wierszy z tabeli part_data przy użyciu kolumny part_id i blokuje również inne rekordy.

Więc masz dwie opcje

  1. Bądź part_id swój klucz podstawowy (jeśli to możliwe)

    lub

  2. Alter oświadczenie w następujący sposób. (Zakładając, że nie jest kluczem podstawowym)

    Select primary_key into #temp From part_data where part_id = @p0

    - dostaniesz klucz podstawowy z tych zapisów, które mają być usunięte

    delete a from part_data a join #temp b on a.primary_key = b.primary_key

- Można usuń te rekordy, które mają zostać usunięte bez blokowania innych rekordów.