Widzimy pewne zgubne, ale rzadkie warunki zakleszczenia w bazie danych Stack Overflow SQL Server 2005.
Podłączyłem profiler, skonfigurowałem profil śledzenia, korzystając z tego doskonałego artykułu na temat rozwiązywania problemów z zakleszczeniami i uchwyciłem kilka przykładów. Dziwne jest to, że zapis zakleszczenia jest zawsze taki sam :
UPDATE [dbo].[Posts]
SET [AnswerCount] = @p1, [LastActivityDate] = @p2, [LastActivityUserId] = @p3
WHERE [Id] = @p0
Druga instrukcja zakleszczenia jest różna, ale zwykle jest to jakiś trywialny, prosty odczyt tabeli wpisów. Ten zawsze ginie w impasie. Oto przykład
SELECT
[t0].[Id], [t0].[PostTypeId], [t0].[Score], [t0].[Views], [t0].[AnswerCount],
[t0].[AcceptedAnswerId], [t0].[IsLocked], [t0].[IsLockedEdit], [t0].[ParentId],
[t0].[CurrentRevisionId], [t0].[FirstRevisionId], [t0].[LockedReason],
[t0].[LastActivityDate], [t0].[LastActivityUserId]
FROM [dbo].[Posts] AS [t0]
WHERE [t0].[ParentId] = @p0
Aby było jasne, nie widzimy zakleszczeń zapisu / zapisu, ale odczytu / zapisu.
W tej chwili mamy mieszankę zapytań LINQ i sparametryzowanych zapytań SQL. Dodaliśmy with (nolock)
do wszystkich zapytań SQL. To mogło niektórym pomóc. Mieliśmy również jedno (bardzo) słabo napisane zapytanie dotyczące znaczka, które wczoraj naprawiłem, które za każdym razem zajmowało 20 sekund, a do tego było uruchamiane co minutę. Miałem nadzieję, że to było przyczyną niektórych problemów z blokowaniem!
Niestety około 2 godziny temu wyskoczył mi kolejny błąd zakleszczenia. Te same dokładne objawy, napisz dokładnie ten sam winowajca.
Naprawdę dziwne jest to, że instrukcja blokująca write SQL, którą widzisz powyżej, jest częścią bardzo specyficznej ścieżki kodu. Jest wykonywany tylko po dodaniu nowej odpowiedzi do pytania - aktualizuje pytanie nadrzędne o nową liczbę odpowiedzi i ostatnią datę / użytkownika. Nie jest to oczywiście aż tak powszechne w porównaniu z ogromną liczbą odczytów, które wykonujemy! O ile wiem, nigdzie w aplikacji nie wykonujemy ogromnej liczby zapisów.
Zdaję sobie sprawę, że NOLOCK to coś w rodzaju gigantycznego młotka, ale większość zapytań, które tu wykonujemy, nie musi być tak dokładna. Czy będzie Cię obchodzić, jeśli Twój profil użytkownika jest nieaktualny o kilka sekund?
Używanie NOLOCK z Linq jest nieco trudniejsze, o czym mówi tutaj Scott Hanselman .
Flirtujemy z pomysłem użycia
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
w podstawowym kontekście bazy danych, tak aby wszystkie nasze zapytania LINQ miały ten zestaw. Bez tego musielibyśmy zawijać każde wywołanie LINQ, które wykonujemy (cóż, proste odczyty, które stanowią zdecydowaną większość) w 3-4-wierszowym bloku kodu transakcji, co jest brzydkie.
Myślę, że jestem trochę sfrustrowany, że trywialne odczyty w SQL 2005 mogą blokować zapisy. Widziałem, że zakleszczenia zapisu / zapisu są dużym problemem, ale czyta? Nie prowadzimy tutaj witryny bankowej, nie potrzebujemy za każdym razem doskonałej dokładności.
Pomysły? Myśli?
Czy tworzysz wystąpienie nowego obiektu LINQ to SQL DataContext dla każdej operacji, czy może udostępniasz ten sam kontekst statyczny dla wszystkich wywołań?
Jeremy, w większości udostępniamy jeden statyczny kontekst danych w podstawowym kontrolerze:
private DBContext _db;
/// <summary>
/// Gets the DataContext to be used by a Request's controllers.
/// </summary>
public DBContext DB
{
get
{
if (_db == null)
{
_db = new DBContext() { SessionName = GetType().Name };
//_db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
}
return _db;
}
}
Czy zalecamy, abyśmy tworzyli nowy kontekst dla każdego kontrolera, dla każdej strony, czy ... częściej?
źródło
Odpowiedzi:
Według MSDN:
http://msdn.microsoft.com/en-us/library/ms191242.aspx
Wydaje się, że istnieje niewielki spadek wydajności w przypadku dodatkowego obciążenia, ale może być nieistotny. Powinniśmy przetestować, aby się upewnić.
Spróbuj ustawić tę opcję i USUŃ wszystkie NOLOCK z zapytań kodu, chyba że jest to naprawdę konieczne. Rozwiązania NOLOCK lub używanie metod globalnych w obsłudze kontekstu bazy danych w celu zwalczania poziomów izolacji transakcji w bazie danych są środkami pomocniczymi tego problemu. NOLOCKS maskuje podstawowe problemy z naszą warstwą danych i może prowadzić do wybierania niewiarygodnych danych, w przypadku których rozwiązaniem wydaje się automatyczne wybieranie / aktualizowanie wersji wierszy.
ALTER Database [StackOverflow.Beta] SET READ_COMMITTED_SNAPSHOT ON
źródło
NOLOCK i READ UNCOMMITTED to śliskie zbocze. Nigdy nie powinieneś ich używać, chyba że najpierw zrozumiesz, dlaczego dochodzi do impasu. Martwiłbym się, gdybyś powiedział: „Dodaliśmy (nolock) do wszystkich zapytań SQL”. Konieczność dodawania Z NOLOCK wszędzie jest pewnym znakiem, że masz problemy z warstwą danych.
Samo oświadczenie o aktualizacji wygląda nieco problematycznie. Czy określasz liczbę wcześniej w transakcji, czy po prostu wyciągasz ją z obiektu?
AnswerCount = AnswerCount+1
dodanie pytania jest prawdopodobnie lepszym sposobem rozwiązania tego problemu. Wtedy nie potrzebujesz transakcji, aby uzyskać prawidłową liczbę, i nie musisz się martwić problemem współbieżności, na który potencjalnie się narażasz.Jednym z łatwych sposobów obejścia tego typu problemu zakleszczenia bez dużego nakładu pracy i bez włączania brudnych odczytów jest użycie
"Snapshot Isolation Mode"
(nowość w SQL 2005), która zawsze zapewni czysty odczyt ostatnich niezmodyfikowanych danych. Możesz również dość łatwo przechwycić i ponawiać zakleszczone instrukcje, jeśli chcesz sobie z nimi poradzić.źródło
Pytanie OP dotyczyło przyczyny wystąpienia tego problemu. Ten post ma nadzieję odpowiedzieć na to pytanie, pozostawiając możliwe rozwiązania do wypracowania przez innych.
Jest to prawdopodobnie kwestia związana z indeksem. Na przykład, powiedzmy, że tabela Posts ma nieklastrowany indeks X, który zawiera identyfikator ParentID i jedno (lub więcej) aktualizowanych pól (AnswerCount, LastActivityDate, LastActivityUserId).
Zakleszczenie wystąpiłoby, gdyby polecenie SELECT cmd wykonało blokadę współdzielonego odczytu w indeksie X w celu wyszukiwania według ParentId, a następnie musi wykonać blokadę współdzielonego odczytu w indeksie klastrowym, aby uzyskać pozostałe kolumny, podczas gdy polecenie UPDATE cmd wykonuje wyłącznie zapis zablokuj indeks klastrowy i musisz uzyskać blokadę na wyłączność zapisu w indeksie X, aby go zaktualizować.
Masz teraz sytuację, w której A zablokował X i próbuje uzyskać Y, podczas gdy B zablokował Y i próbuje uzyskać X.
Oczywiście będziemy potrzebować OP, aby zaktualizować swój post o więcej informacji dotyczących indeksów w grze, aby potwierdzić, czy jest to rzeczywiście przyczyna.
źródło
Nie podoba mi się to pytanie i towarzysząca mu odpowiedź. Jest dużo „spróbuj tego magicznego pyłu! Nie, tego magicznego pyłu!”
Nigdzie nie widzę, żebyś przeanalizował, jakie zamki są zajęte, i określił, jaki dokładny typ zamków jest zakleszczony.
Wskazałeś tylko, że występują pewne blokady - a nie to, co jest zakleszczeniem.
W SQL 2005 możesz uzyskać więcej informacji o tym, jakie blokady są usuwane, używając:
dzięki czemu w przypadku zakleszczenia będziesz mieć lepszą diagnostykę.
źródło
Czy tworzysz nowy obiekt LINQ to SQL DataContext dla każdej operacji, czy może udostępniasz ten sam kontekst statyczny dla wszystkich wywołań? Pierwotnie wypróbowałem to drugie podejście iz tego, co pamiętam, spowodowało to niechciane zablokowanie bazy danych. Teraz tworzę nowy kontekst dla każdej operacji atomowej.
źródło
Przed spaleniem domu, aby złapać muchę z NOLOCKem na całym świecie, możesz rzucić okiem na ten wykres impasu, który powinieneś był zarejestrować za pomocą Profiler.
Pamiętaj, że zakleszczenie wymaga (co najmniej) 2 zamków. Połączenie 1 ma Blokadę A, chce Zamku B - i odwrotnie w przypadku Połączenia 2. To jest sytuacja nierozwiązywalna i ktoś musi dać.
To, co pokazałeś do tej pory, jest rozwiązane przez proste blokowanie, które Sql Server chętnie wykonuje przez cały dzień.
Podejrzewam, że ty (lub LINQ) rozpoczynasz transakcję z tym oświadczeniem UPDATE i wybierasz wcześniej jakąś inną informację. Ale naprawdę trzeba cofnąć się przez wykres zakleszczenia, aby znaleźć blokady utrzymywane przez każdy wątek, a następnie cofnąć się za pomocą programu Profiler, aby znaleźć instrukcje, które spowodowały przyznanie tych blokad.
Spodziewam się, że są co najmniej 4 instrukcje, aby ukończyć tę zagadkę (lub stwierdzenie, które przyjmuje wiele blokad - być może w tabeli Posts jest wyzwalacz?).
źródło
Nie - to jest całkowicie do przyjęcia. Ustawienie podstawowego poziomu izolacji transakcji jest prawdopodobnie najlepszym / najczystszym sposobem.
źródło
Typowa blokada odczytu / zapisu wynika z dostępu do kolejności indeksowania. Odczyt (T1) lokalizuje wiersz w indeksie A, a następnie wyszukuje kolumnę rzutowaną w indeksie B (zwykle w klastrze). Zapis (T2) zmienia indeks B (klaster) musi następnie zaktualizować indeks A. T1 ma S-Lck na A, chce S-Lck na B, T2 ma X-Lck na B, chce U-Lck na A. Zakleszczenie , ptyś. T1 zostaje zabity. Jest to powszechne w środowiskach o dużym ruchu OLTP i tylko odrobinę za dużo indeksów :). Rozwiązaniem jest, aby odczyt nie musiał przeskakiwać z A do B (tj. Uwzględniać kolumnę w A lub usuwać kolumnę z listy rzutowanej), albo T2 nie musiał przeskakiwać z B do A (nie aktualizować indeksowanej kolumny). Niestety linq nie jest tutaj twoim przyjacielem ...
źródło
@Jeff - zdecydowanie nie jestem ekspertem w tej dziedzinie, ale mam dobre wyniki w tworzeniu nowego kontekstu w prawie każdym połączeniu. Myślę, że jest to podobne do tworzenia nowego obiektu Connection przy każdym połączeniu z ADO. Narzut nie jest tak zły, jak mogłoby się wydawać, ponieważ pule połączeń i tak będą nadal używane.
Po prostu używam globalnego pomocnika statycznego, takiego jak ten:
a potem robię coś takiego:
var db = AppData.DB; var results = from p in db.Posts where p.ID = id select p;
I zrobiłbym to samo dla aktualizacji. W każdym razie nie mam prawie tak dużego ruchu jak ty, ale zdecydowanie blokowałem się, gdy wcześnie korzystałem z udostępnionego DataContext z zaledwie garstką użytkowników. Brak gwarancji, ale warto spróbować.
Aktualizacja : Z drugiej strony, patrząc na swój kod, udostępniasz kontekst danych tylko przez cały okres istnienia tej konkretnej instancji kontrolera, co w zasadzie wydaje się w porządku, chyba że w jakiś sposób jest używany jednocześnie przez wiele wywołań w kontrolerze. W wątku na ten temat ScottGu powiedział:
W każdym razie może to nie być to, ale znowu prawdopodobnie warto spróbować, być może w połączeniu z niektórymi testami obciążenia.
źródło
P: Dlaczego przechowujesz plik
AnswerCount
wPosts
w pierwszej kolejności tabeli?Alternatywnym podejściem jest wyeliminowanie „odpisu” do
Posts
tabeli przez nie zapisywanieAnswerCount
w tabeli, ale dynamiczne obliczanie liczby odpowiedzi na post zgodnie z wymaganiami.Tak, to oznacza, że uruchamiasz dodatkowe zapytanie:
SELECT COUNT(*) FROM Answers WHERE post_id = @id
lub częściej (jeśli wyświetlasz to na stronie głównej):
SELECT p.post_id, p.<additional post fields>, a.AnswerCount FROM Posts p INNER JOIN AnswersCount_view a ON <join criteria> WHERE <home page criteria>
ale zazwyczaj skutkuje to
INDEX SCAN
i może być bardziej wydajne w wykorzystaniu zasobów niż używanieREAD ISOLATION
.Jest więcej niż jeden sposób na oskórowanie kota. Przedwczesna denormalizacja schematu bazy danych może spowodować problemy ze skalowalnością.
źródło
Zdecydowanie chcesz, aby READ_COMMITTED_SNAPSHOT był włączony, co nie jest domyślnie. To daje semantykę MVCC. Jest to ta sama rzecz, której Oracle używa domyślnie. Posiadanie bazy danych MVCC jest tak niesamowicie przydatne, że NIE jej używanie jest szalone. Pozwala to na wykonywanie następujących czynności w ramach transakcji:
Zaktualizuj USERS Set FirstName = 'foobar'; // zdecyduj się przespać rok.
w międzyczasie bez zobowiązania się do powyższego, każdy może nadal wybierać z tej tabeli. Jeśli nie znasz MVCC, będziesz w szoku, że kiedykolwiek mogłeś bez niego żyć. Poważnie.
źródło
Ustawienie domyślnego odczytu niezatwierdzonych nie jest dobrym pomysłem. Twoja niewątpliwie wprowadzisz niespójności i skończysz z problemem gorszym niż ten, który masz teraz. Izolacja migawek może działać dobrze, ale jest to drastyczna zmiana w sposobie działania Sql Server i stawia ogromną obciążenie tempdb.
Oto, co powinieneś zrobić: użyj try-catch (w T-SQL), aby wykryć stan zakleszczenia. Kiedy to się stanie, po prostu ponownie uruchom zapytanie. Jest to standardowa praktyka programowania baz danych.
Dobre przykłady tej techniki można znaleźć w Biblii Paula Nielsona Sql Server 2005 .
Oto szybki szablon, którego używam:
-- Deadlock retry template declare @lastError int; declare @numErrors int; set @numErrors = 0; LockTimeoutRetry: begin try; -- The query goes here return; -- this is the normal end of the procedure end try begin catch set @lastError=@@error if @lastError = 1222 or @lastError = 1205 -- Lock timeout or deadlock begin; if @numErrors >= 3 -- We hit the retry limit begin; raiserror('Could not get a lock after 3 attempts', 16, 1); return -100; end; -- Wait and then try the transaction again waitfor delay '00:00:00.25'; set @numErrors = @numErrors + 1; goto LockTimeoutRetry; end; -- Some other error occurred declare @errorMessage nvarchar(4000), @errorSeverity int select @errorMessage = error_message(), @errorSeverity = error_severity() raiserror(@errorMessage, @errorSeverity, 1) return -100 end catch;
źródło
Jedna rzecz, która działała w przeszłości, to upewnianie się, że wszystkie moje zapytania i aktualizacje mają dostęp do zasobów (tabel) w tej samej kolejności.
Oznacza to, że jeśli jedno zapytanie aktualizuje w kolejności Tabela1, Tabela2, a inne zapytanie aktualizuje je w kolejności Tabela2, Tabela1, mogą wystąpić zakleszczenia.
Nie jestem pewien, czy możesz zmienić kolejność aktualizacji, ponieważ używasz LINQ. Ale jest na co popatrzeć.
źródło
Kilka sekund zdecydowanie byłoby do zaakceptowania. Zresztą nie wydaje się, żeby to trwało tak długo, chyba że ogromna liczba osób udzieliłaby odpowiedzi w tym samym czasie.
źródło
Zgadzam się z Jeremym w tej sprawie. Pytasz, czy powinieneś utworzyć nowy kontekst danych dla każdego kontrolera lub dla każdej strony - mam tendencję do tworzenia nowego kontekstu dla każdego niezależnego zapytania.
Obecnie buduję rozwiązanie, które kiedyś implementowało kontekst statyczny, tak jak ty, a kiedy rzucałem tony żądań na bestię serwera (milion +) podczas testów warunków skrajnych, otrzymywałem również losowo blokady odczytu / zapisu.
Gdy tylko zmieniłem strategię, aby używać innego kontekstu danych na poziomie LINQ na zapytanie i zaufałem, że serwer SQL może działać w swojej magii puli połączeń, blokady wydawały się znikać.
Oczywiście byłem pod presją czasu, więc próbowałem wielu rzeczy w tym samym czasie, więc nie mogę być w 100% pewien, że to naprawiło, ale mam wysoki poziom pewności - powiedzmy to w ten sposób .
źródło
Powinieneś zaimplementować brudne odczyty.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
Jeśli absolutnie nie potrzebujesz doskonałej integralności transakcyjnej ze swoimi zapytaniami, powinieneś używać brudnych odczytów podczas uzyskiwania dostępu do tabel o wysokiej współbieżności. Zakładam, że jednym z nich byłby Twój stół Posty.
Może to dać tak zwane „odczyty fantomowe”, czyli wtedy, gdy zapytanie działa na danych z transakcji, która nie została zatwierdzona.
Użyj brudnych odczytów. Masz rację, że nie zapewnią Ci idealnej dokładności, ale powinny wyjaśnić Twoje problemy z martwymi blokadami.
Jeśli zaimplementujesz brudne odczyty w „kontekście bazy danych”, zawsze możesz opakować swoje indywidualne wywołania przy użyciu wyższego poziomu izolacji, jeśli potrzebujesz integralności transakcyjnej.
źródło
Więc jaki jest problem z implementacją mechanizmu ponawiania? Zawsze będzie możliwość wystąpienia impasu, więc dlaczego nie mieć logiki, aby go zidentyfikować i po prostu spróbować ponownie?
Czy przynajmniej niektóre inne opcje nie wprowadzą kar za wydajność, które są pobierane przez cały czas, gdy system ponawiania rzadko uruchamia się?
Nie zapomnij też o jakimś logowaniu, gdy nastąpi ponowna próba, aby nie wpaść w tę rzadką sytuację, która często się zdarza.
źródło
Teraz, gdy widzę odpowiedź Jeremy'ego, myślę, że słyszałem, że najlepszą praktyką jest użycie nowego DataContext dla każdej operacji na danych. Rob Conery napisał kilka postów na temat DataContext i zawsze je publikuje, zamiast używać singletona.
Oto wzorzec, którego użyliśmy dla Video.Show ( link do widoku źródła w CodePlex ):
Następnie na poziomie usług (lub bardziej szczegółowym w przypadku aktualizacji):
private VideoShowDataContext dataContext = DataContextFactory.DataContext(); public VideoSearchResult GetVideos(int pageSize, int pageNumber, string sortType) { var videos = from video in DataContext.Videos where video.StatusId == (int)VideoServices.VideoStatus.Complete orderby video.DatePublished descending select video; return GetSearchResult(videos, pageSize, pageNumber); }
źródło
Musiałbym zgodzić się z Gregiem, o ile ustawienie poziomu izolacji na odczyt niezatwierdzonych nie ma żadnego złego wpływu na inne zapytania.
Chciałbym wiedzieć, Jeff, jak ustawienie tego na poziomie bazy danych wpłynęłoby na zapytanie, takie jak następujące:
Begin Tran Insert into Table (Columns) Values (Values) Select Max(ID) From Table Commit Tran
źródło
Nie przeszkadza mi, jeśli mój profil jest nieaktualny nawet o kilka minut.
Czy próbujesz ponownie odczytać po niepowodzeniu? Z pewnością jest możliwe, gdy wystrzeliwujesz mnóstwo losowych odczytów, że kilka trafi, gdy nie będą mogli odczytać. Większość aplikacji, z którymi pracuję, to bardzo niewiele zapisów w porównaniu z liczbą odczytów i jestem pewien, że odczyty nie są zbliżone do liczby, którą otrzymujesz.
Jeśli zaimplementowanie polecenia „ODCZYTAJ NIEPOWODZONY” nie rozwiązuje problemu, trudno jest pomóc, nie wiedząc dużo więcej o przetwarzaniu. Może istnieć inna opcja dostrajania, która pomogłaby w tym zachowaniu. O ile jakiś guru MSSQL nie przyjdzie na ratunek, polecam zgłoszenie problemu dostawcy.
źródło
Nadal bym wszystko dostroił; jak działa podsystem dyskowy? Jaka jest średnia długość kolejki dyskowej? Jeśli we / wy są tworzone kopie zapasowe, prawdziwym problemem mogą nie być te dwa zapytania, które są zakleszczone, może to być inne zapytanie, które blokuje system; wspomniałeś o zapytaniu trwającym 20 sekund, które zostało dostrojone, czy są inne?
Skup się na skróceniu długo działających zapytań, założę się, że problemy z zakleszczeniem znikną.
źródło
Wystąpił ten sam problem i nie można użyć parametru „IsolationLevel = IsolationLevel.ReadUncommitted” w TransactionScope, ponieważ serwer nie ma włączonego DTS (!).
Oto, co zrobiłem z metodą rozszerzenia:
public static void SetNoLock(this MyDataContext myDS) { myDS.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"); }
Dlatego dla wybranych, którzy używają krytycznych tabel współbieżności, włączamy „nolock” w następujący sposób:
Sugestie są mile widziane!
źródło