Diagnozowanie zakleszczeń w SQL Server 2005

82

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?

Jeff Atwood
źródło
2
Jakiego <a href=" en.wikipedia.org/wiki/… trybu</a> używasz, „pesymistycznego” (oparty na blokadach) czy „optymistycznego” (<a href = " en.wikipedia.org/wiki/ … )?
John Siracusa,
Zgadzam się z odpowiedzią Guya powyżej - zamiast próbować obejść objaw, dlaczego nie zająć się podstawowymi przyczynami? Dodając bieżącą sumę AnswerCount do tabeli Posts, utworzyłeś potencjalny zasób blokujący. Czy Jeff chciałby opublikować swoje ERD dla StackOverflow, aby ludzie mogli krytykować?
andyp
2
Wow - usłyszałem o tym w podcastie ze Scottem. Nie mogę uwierzyć, że nie jest wysyłany po wyjęciu z pudełka z lepszą konfiguracją. Pokażę to naszym DBA (ponieważ oni też często używają 'nolock')
Dan Esparza,
3
Zobacz: samsaffron.com/archive/2008/08/27/Deadlocked+ z powodu zakleszczenia. Włączenie izolacji migawek jest dobrym rozwiązaniem tego problemu.
Sam Saffron

Odpowiedzi:

44

Według MSDN:

http://msdn.microsoft.com/en-us/library/ms191242.aspx

Gdy opcje bazy danych READ COMMITTED SNAPSHOT lub ALLOW SNAPSHOT ISOLATION są WŁĄCZONE, kopie logiczne (wersje) są utrzymywane dla wszystkich modyfikacji danych wykonanych w bazie danych. Za każdym razem, gdy wiersz jest modyfikowany przez określoną transakcję, wystąpienie Aparat baz danych przechowuje wersję wcześniej zatwierdzonego obrazu wiersza w tempdb. Każda wersja jest oznaczona numerem sekwencji transakcji, w której dokonano zmiany. Wersje zmodyfikowanych wierszy są łączone za pomocą listy linków. Najnowsza wartość wiersza jest zawsze przechowywana w bieżącej bazie danych i połączona z wersjami wierszy przechowywanych w tempdb.

W przypadku transakcji krótkoterminowych wersja zmodyfikowanego wiersza może zostać zapisana w pamięci podręcznej w puli buforów bez zapisywania jej w plikach dyskowych bazy danych tempdb. Jeśli potrzeba wersjonowanego wiersza jest krótkotrwała, zostanie po prostu usunięta z puli buforów i niekoniecznie będzie wiązać się z narzutem we / wy.

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
Geoff Dalgas
źródło
3
„NOLOCKS maskuje podstawowe problemy z naszą warstwą danych”… Jakie problemy maskuje NOLOCK? Jeśli myślę, że potrzebuję NOLOCK, jakich problemów powinienem szukać?
Matt Hamilton,
3
Co z tą odpowiedzią sprawia, że ​​jest ona „odpowiedzią”? Nadal nie rozumiem, dlaczego odczyt milisekundowy natychmiast zabiłby zapis? Zgaduję, że odpowiedź „user13484” jest bardzo zła, nie ma do niej odniesienia, jeśli tak jest.
RichardTheKiwi,
37

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+1dodanie 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ć.

JEzell
źródło
4
Jestem z JEzellem - pierwszą rzeczą, na którą wyzerowałem, jest „SET AnswerCount = <fixed value>”. Skąd pochodzi ta wartość? To sprawia, że ​​zastanawiam się, czy w innym miejscu transakcji odzyskałeś ją w sposób, który łapie kilka blokad. Zacząłbym od tego. I tak, globalny NOLOCK to plaster.
Cowan
25

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.

MrB
źródło
Zgadzam się z tą analizą - SELECT i UPDATE przetwarzają wiersze w innej kolejności, więc każdy z nich próbuje uzyskać blokadę wiersza, którą ma drugi.
Mike Dimmick,
Jest to najlepsza odpowiedź na cały ten wątek i stanowi jedyne wyjaśnienie, dlaczego faktycznie występuje impas. Szkoda, że ​​nie jest to odpowiedź nr 1, ponieważ jest tutaj najlepsza.
Jonathan Kehayias,
W niektórych odpowiedziach nie ma sensu, jeśli chodzi o blokadę między 2 atomowymi prostymi instrukcjami. To jedyny post, który próbuje to wyjaśnić. Chociaż instrukcja jest prosta, aktualizacja tabeli może obejmować wiele aktualizacji CIX i NCIX, co rozszerza się na wiele operacji. To samo dotyczy ODCZYTU obejmującego przechodzenie przez NCIX, wyszukiwanie zakładek CIX. Nie ma to nic wspólnego z dołączaniem do stołów w tej samej kolejności itp. (Czy ludzie czytają pytania?)
RichardTheKiwi
18

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:

DBCC TRACEON (1222, -1)

dzięki czemu w przypadku zakleszczenia będziesz mieć lepszą diagnostykę.

Leon Bambrick
źródło
13
Zakleszczenie jest obsługiwane natychmiast przez monitor zakleszczeń w programie SQL Server. DMV są bezużyteczne do rozwiązywania problemów z impasem, ponieważ ofiara zostanie wybrana i zabita, zanim zdążysz to zauważyć.
Jonathan Kehayias,
14

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.

jeremcc
źródło
10

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?).

Mark Brackett
źródło
7

Czy będzie Cię obchodzić, jeśli Twój profil użytkownika jest nieaktualny o kilka sekund?

Nie - to jest całkowicie do przyjęcia. Ustawienie podstawowego poziomu izolacji transakcji jest prawdopodobnie najlepszym / najczystszym sposobem.

Greg Hurlman
źródło
5

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

Remus Rusanu
źródło
BTW A i B to indeksy tej samej tabeli
Remus Rusanu
3

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

public static class AppData
{
    /// <summary>
    /// Gets a new database context
    /// </summary>
    public static CoreDataContext DB
    {
        get
        {
            var dataContext = new CoreDataContext
            {
                DeferredLoadingEnabled = true
            };
            return dataContext;
        }
    }
}

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

Kontrolery żyją tylko dla pojedynczego żądania - więc pod koniec przetwarzania żądania są zbierane jako śmieci (co oznacza, że ​​DataContext jest zbierany) ...

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.

jeremcc
źródło
3

P: Dlaczego przechowujesz plik AnswerCountwPosts w pierwszej kolejności tabeli?

Alternatywnym podejściem jest wyeliminowanie „odpisu” do Poststabeli przez nie zapisywanie AnswerCountw 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 SCANi może być bardziej wydajne w wykorzystaniu zasobów niż używanie READ ISOLATION.

Jest więcej niż jeden sposób na oskórowanie kota. Przedwczesna denormalizacja schematu bazy danych może spowodować problemy ze skalowalnością.

Chłopak
źródło
3

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.

Akwiny
źródło
3

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;    
Eric Z Beard
źródło
2
dlaczego to rozwiązanie sprawia, że ​​się wzdrygam? !! zastanawiałbym się, DLACZEGO jest impas… a nie pomoc dla biednych w związku z problemem.
Pure.Krome
2

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

Michael Sharek
źródło
1

Czy będzie Cię obchodzić, jeśli Twój profil użytkownika jest nieaktualny o kilka sekund?

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.

a_hardin
źródło
1

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 .

RobertTheGrey
źródło
1

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.

Nie prowadzimy tutaj witryny bankowej, nie potrzebujemy za każdym razem doskonałej dokładności

Użyj brudnych odczytów. Masz rację, że nie zapewnią Ci idealnej dokładności, ale powinny wyjaśnić Twoje problemy z martwymi blokadami.

Bez tego musielibyśmy zawijać każde wywołanie LINQ, które wykonujemy (cóż, proste odczytywanie, które jest zdecydowaną większością) w 3-4-wierszowym bloku kodu transakcji, co jest brzydkie

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.

Seibar
źródło
1

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.

John Dyer
źródło
1

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

using System.Configuration;
namespace VideoShow.Data
{
  public class DataContextFactory
  {
    public static VideoShowDataContext DataContext()
    {
        return new VideoShowDataContext(ConfigurationManager.ConnectionStrings["VideoShowConnectionString"].ConnectionString);
    }
    public static VideoShowDataContext DataContext(string connectionString)
    {
        return new VideoShowDataContext(connectionString);
    }
  }
}

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);
}
Jon Galloway
źródło
0

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
GateKiller
źródło
0

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.

bruceatk
źródło
0

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

SqlACID
źródło
0

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:

using (MyDataContext myDS = new MyDataContext())
{
   myDS.SetNoLock();

   //  var query = from ...my dirty querys here...
}

Sugestie są mile widziane!


źródło