Database
 sql >> Baza danych >  >> RDS >> Database

Niezamierzone skutki uboczne – sesje snu trzymające kłódki

Ostatnie zaangażowanie konsultingowe koncentrowało się na blokowaniu problemów wewnątrz SQL Server, które powodowały opóźnienia w przetwarzaniu żądań użytkowników z aplikacji. Gdy zaczęliśmy zagłębiać się w napotkane problemy, stało się jasne, że z punktu widzenia SQL Server problem dotyczył sesji w stanie uśpienia, które blokowały wewnątrz silnika. Nie jest to typowe zachowanie SQL Server, więc najpierw pomyślałem, że istnieje jakaś wada projektu aplikacji, która pozostawia aktywną transakcję w sesji, która została zresetowana do puli połączeń w aplikacji, ale szybko okazało się, że nie ponieważ blokady były później zwalniane automatycznie, nastąpiło tylko opóźnienie. Musieliśmy więc dalej kopać.

Zrozumienie stanu sesji

W zależności od tego, który DMV patrzysz na SQL Server, sesja może mieć kilka różnych statusów. Stan uśpienia oznacza, że ​​silnik wykonał polecenie, wszystko między klientem a serwerem zakończyło interakcję, a połączenie czeka na następne polecenie od klienta. Jeśli śpiąca sesja ma otwartą transakcję, jest ona zawsze związana z kodem, a nie z SQL Serverem. Otwartą transakcję można wytłumaczyć kilkoma rzeczami. Pierwsza możliwość to procedura z jawną transakcją, która nie włącza ustawienia XACT_ABORT, a następnie kończy się limitem czasu bez poprawnej obsługi czyszczenia aplikacji, jak wyjaśniono w tym naprawdę starym poście przez zespół CSS:

  • Jak to działa:co to jest uśpienie / oczekiwanie na sesję poleceń

Gdyby procedura włączyła ustawienie XACT_ABORT, transakcja zostałaby przerwana automatycznie po przekroczeniu limitu czasu i transakcja zostałaby wycofana. SQL Server robi dokładnie to, co jest wymagane zgodnie ze standardami ANSI i utrzymuje właściwości ACID wykonanego polecenia. Limit czasu nie jest związany z programem SQL Server, jest ustawiany przez klienta .NET i właściwość CommandTimeout, więc również jest związany z kodem, a nie z zachowaniem aparatu SQL. Jest to ten sam problem, o którym mówiłem w mojej serii wydarzeń rozszerzonych, w tym poście na blogu:

  • Korzystanie z wielu celów do debugowania osieroconych transakcji

Jednak w tym przypadku aplikacja nie korzystała z procedur składowanych w celu uzyskania dostępu do bazy danych, a cały kod został wygenerowany przez ORM. W tym momencie dochodzenie odeszło od SQL Server i bardziej w kierunku tego, w jaki sposób aplikacja korzysta z ORM i gdzie transakcje będą generowane przez bazę kodu aplikacji.

Zrozumienie transakcji .NET

Powszechnie wiadomo, że SQL Server opakowuje każdą modyfikację danych w transakcję, która jest automatycznie zatwierdzana, chyba że ustawiona opcja IMPLICIT_TRANSACTIONS jest WŁĄCZONA dla sesji. Po sprawdzeniu, że nie jest to włączone dla żadnej części ich kodu, można było dość bezpiecznie założyć, że wszelkie transakcje pozostałe po sesji w trybie uśpienia były wynikiem jawnej transakcji otwartej gdzieś podczas wykonywania ich kodu. Teraz chodziło tylko o zrozumienie, kiedy, gdzie i co najważniejsze, dlaczego nie została natychmiast zamknięta. Prowadzi to do jednego z kilku różnych scenariuszy, których będziemy musieli szukać w ich kodzie warstwy aplikacji:

  • Aplikacja używająca TransactionScope() wokół operacji
  • Aplikacja rejestrująca SqlTransaction() w połączeniu
  • Kod ORM opakowujący wewnętrznie określone wywołania w transakcji, która nie jest realizowana

Dokumentacja TransactionScope dość szybko wykluczyła to jako możliwą przyczynę tego. Jeśli nie uda Ci się Ukończyć zakresu transakcji, transakcja zostanie automatycznie wycofana i przerwana po jej zbyciu, więc jest mało prawdopodobne, że utrzyma się to podczas resetowania połączenia. Podobnie obiekt SqlTransaction zostanie automatycznie wycofany, jeśli nie zostanie zatwierdzony, gdy połączenie zostanie zresetowane do puli połączeń, dzięki czemu szybko stanie się nie-rozpoczęciem problemu. To właśnie opuściło generowanie kodu ORM, przynajmniej tak myślałem, i byłoby niesamowicie dziwne, gdyby starsza wersja bardzo popularnego ORM wykazywała tego typu zachowanie z mojego doświadczenia, więc musieliśmy zagłębić się dalej.

Dokumentacja używanego przez nich ORM wyraźnie stwierdza, że ​​gdy ma miejsce jakakolwiek czynność wielopodmiotowa, jest ona wykonywana w ramach transakcji. Działania na wielu encjach mogą być zapisywaniem rekurencyjnym lub zapisywaniem kolekcji encji z powrotem do bazy danych z aplikacji, a programiści zgodzili się, że tego typu operacje występują w całym ich kodzie, więc tak, ORM musi używać transakcji, ale dlaczego nagle staje się problemem.

Podstawa problemu

W tym momencie cofnęliśmy się o krok i zaczęliśmy przeprowadzać holistyczny przegląd całego środowiska, korzystając z New Relic i innych narzędzi monitorujących, które były dostępne, gdy pojawiały się problemy z blokowaniem. Stało się jasne, że uśpione sesje utrzymujące blokady miały miejsce tylko wtedy, gdy serwery aplikacji IIS były mocno obciążone procesorem, ale samo to nie wystarczało, aby uwzględnić opóźnienie, które było widoczne w zatwierdzaniu transakcji zwalniających blokady. Okazało się również, że serwery aplikacji były maszynami wirtualnymi uruchomionymi na przeciążonym hoście hypervisor, a czasy oczekiwania na CPU Ready dla nich były znacznie podwyższone w momencie problemów z blokowaniem na podstawie wartości sumarycznych dostarczonych przez administratora VM.

Stan uśpienia wystąpi z otwartą transakcją trzymającą blokady między wywołaniami .SaveEntity obiektów kończących a ostatecznym zatwierdzeniem w kodzie wygenerowanym za obiektami. Jeśli serwer VM/App jest pod presją lub jest obciążony, może to być opóźnione i prowadzić do problemów z blokowaniem, ale problem nie dotyczy SQL Server, robi dokładnie to, co powinien w zakresie transakcji. Problem jest ostatecznie wynikiem opóźnienia w przetwarzaniu punktu zatwierdzenia po stronie aplikacji. Pobieranie czasów ukończenia instrukcji i zakończonych zdarzeń RPC z rozszerzonych zdarzeń wraz z chronometrażem zdarzenia database_transaction_end pokazuje opóźnienie w obie strony z warstwy aplikacji zamykającej transakcję w otwartym połączeniu. W tym przypadku wszystko, co widać w SQL Server, jest ofiarą przeciążonego serwera aplikacji i przeciążonego hosta maszyny wirtualnej. Przenoszenie/podział obciążenia aplikacji na serwery w konfiguracji równoważenia obciążenia sieciowego lub sprzętowego z równoważeniem obciążenia przy użyciu hostów, które nie są nadmiernie zatwierdzone przy użyciu procesora, szybko przywróciłoby natychmiastowe zatwierdzenie transakcji i usunęłoby sesje uśpienia utrzymujące blokady w programie SQL Server.

Jeszcze jeden przykład problemu środowiskowego powodującego coś, co wyglądało na zwykły problem z blokowaniem. Zawsze opłaca się zbadać, dlaczego wątek blokujący nie jest w stanie szybko zwolnić blokad.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wykonywanie audytu zmian danych przy użyciu tabeli czasowej

  2. Zdecydowanie wpisz te parametry wyceniane w tabeli

  3. Anonimizacja identyfikatorów pośrednich w celu zmniejszenia ryzyka ponownej identyfikacji

  4. Jak utworzyć tabelę z wieloma kluczami obcymi i nie pomylić się?

  5. Podstawy wyrażeń tabelarycznych, część 11 – widoki, uwagi dotyczące modyfikacji