5

Mamy problem z niektórymi kodami bazy danych, które najwyraźniej są wykonywane z niewłaściwym poziomem izolacji. W tej części kodu należy wykonać polecenie "READ UNCOMMITTED", aby zminimalizować blokady. Niespójne dane są w tym momencie prawidłowe."USTAWIENIE POZIOMU ​​IZOLACJI PRZESYŁANIA CZYTA NIEKOMMITOWANE" nie jest używane? Czy patrzę w niewłaściwy sposób?

Jednak kod faktycznie czyta się z READ COMMITTED, a my nie możemy zrozumieć dlaczego.

Oto co zrobiliśmy:

  1. Otwórz połączenie
  2. wykonać na tym związku "SET TRANSAKCJI IZOLACJA LEVEL READ UNCOMMITTED"
  3. Hit punkt przerwania
  4. Execute SQL

W punkcie przerwania wydajemy to polecenie do bazy danych:

select s.session_id, s.transaction_isolation_level, st.text from sys.dm_exec_sessions s 
inner join sys.sysprocesses sp on (sp.spid = s.session_id) 
CROSS APPLY sys.dm_exec_sql_text(sp.sql_handle) st 

Ten SQL raporty zbiorcze 4 połączenia w tej chwili, z których jeden jest nasz związek, że możemy krok poza przerwania wykonać naszą SQL z, który ma ten stan:

53 2 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 

tj. sesja 53 ma poziom izolacji 2 (READ COMMITTED), a ostatnim SQL, który został wykonany w tej sesji, było polecenie "SET TRANSACTION ...".

Jak to możliwe?

W programie SQL Profiler zweryfikowaliśmy, że to połączenie nie było aktywne, zanim nasz kod .NET go otworzył, więc nie został użyty ponownie z puli połączeń.

Jednak z nowym połączeniem i jedynym wykonanym na nim SQL, który wyraźnie powiedział, że powinien używać polecenia READ UNCOMMITTED, w jaki sposób połączenie może nadal być READ COMMITTED?

Na co powinniśmy tutaj patrzeć?

Łańcuch łączący (bitami redacted) jest tak:

SERVER=hostname;DATABASE=dbname;Integrated Security=false;USER ID=sa;PASSWORD=****;Application Name=appname;Type System Version=SQL Server 2000;Workstation ID=hostname; 

połączenia są normalne SqlConnection połączenia otwarte w normalny sposób.

Niestety nie jesteśmy w stanie odtworzyć problemu, jeśli piszemy normalny kod otwierający SqlConnection, więc musi być coś ze stanem aplikacji, ale ponieważ SqlProfiler i Sql Server mówią nam, że tak, SQL został wykonany, ale nie, nie obchodzi mnie to.

Co może wpłynąć na to?

Ten sam kod otwiera również inne połączenia, to jest kod jest wykonywany wiele razy i otwiera wiele połączeń, więc więcej niż jedno połączenie kończy się w puli, ale tylko pierwsze połączenie kończy się mając ten problem.

To jest SQL Server 2008 R2 i odtworzyliśmy ten problem również w 2012 roku.

Edit

OK, niektóre więcej informacji.

pierwsze, umożliwiają łączenie, a raczej nie jesteśmy wyraźnie wyłączając go, ani nie jesteśmy kręci ciąg połączenia, aby baseny „n”.

Jednak ta gra jest pierwszą otwierane z tego konkretnego ciągu połączenia, zatem nie jest pobierana z puli. Zobacz także moją notatkę poniżej o tym, że jest trwale "chora".

To połączenie jest tworzone tak:

var conn = new SqlConnection(...); 
conn.StateChance += connection_StateChange; 

private void connection_StateChange(Object sender, StateChangeEventArgs e) 
{ 
    if (e.CurrentState == ConnectionState.Open) 
    { 
     using (IDbCommand cmd = ((SqlConnection)sender).CreateCommand()) 
     { 
      cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; 
      cmd.ExecuteNonQuery(); 
     } 

My nie wykonując żadnej innej SQL wcześniej.

Należy pamiętać, że ten kod jest używany wiele razy razy w czasie trwania aplikacji, jest to tylko pierwsze połączenie, które otwiera, które kończy się źle.

To połączenie również staje się trwale chora. Ponieważ za każdym razem, gdy otwieramy połączenie (nawet jeśli możemy je wydostać z puli połączeń), wykonuje się powyższe zdarzenie zmiany stanu, próbując ponownie ustawić poziom izolacji. To również się nie udaje, ale tylko dla tego pojedynczego połączenia.

Dodatkowo znaleźliśmy jedną rzecz, która ma na to wpływ, ponieważ opublikowałem to pytanie.

Zmieniając ciąg połączenia, które napisałem powyżej:

...;Type System Version=SQL Server 2000;... 

do tego:

...;Type System Version=SQL Server 2008;MultipleActiveResultSets=true;... 

to ten problem zniknie, przy zerwaniu wymienionych wcześniej, połączenie tej pory „READ NIEZGODNY "stan.

To był czerwony śledzia, połączenie nie było już zgłaszane w naszym przeglądzie, dopóki faktycznie nie wykonaliśmy kodu tam.

Kontynuujemy nasze debugowanie.

+0

Skopiowałem powyższy kod i otworzyłem połączenie, a otrzymałem oczekiwany wynik odczytu niezatwierdzonego połączenia. Musi istnieć inny kod ustawiający twój poziom izolacji –

+0

Czy coś mogło ustawić 'NOEXEC' na' ON' w tym połączeniu? Jeśli zrobię to w jednej partii, a następnie uruchomię 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED' w następnej partii w jednym oknie zapytania, otrzymam dane wyjściowe, które pokazałeś - ostatnio wykonane polecenie to' SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED' kolumna 'transaction_isolation_level' nadal pokazuje, że jest ustawiona na 2. –

+0

Zobacz moją własną odpowiedź tutaj, nie mam pewności, że odkryłem wszystkie niezbędne elementy mojego kodu w moim pytaniu, więc mam dwa umysły na temat zachowania go tutaj, Nie wydaje mi się, żeby to było dobre pytanie. –

Odpowiedz

4

Problem polega na tym, że SqlConnection.BeginTransaction że nie bierze parametrów domyślnych czytać zaangażowana. Chyba nie zrozumieliśmy, jaki jest tekst "domyślnego poziomu izolacji" na tej stronie.

To strona ma ten tekst:

Jeśli nie zostanie określony poziom izolacji, domyślny poziom izolacji jest używany. Aby określić poziom izolacji za pomocą metody BeginTransaction, użyj przeciążenia, które pobiera parametr iso (BeginTransaction). Ustawiony poziom izolacji dla transakcji utrzymuje się po zakończeniu transakcji i dopóki połączenie nie zostanie zamknięte lub usunięte. Ustawienie poziomu izolacji na migawka w bazie danych, w której poziom izolacji migawki nie jest włączony, nie powoduje wyjątku. Transakcja zakończy się przy użyciu domyślnego poziomu izolacji.

(mój highlight)

Oto skrypt LINQPad który demonstruje:

void Main() 
{ 
    using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated security=true")) 
    { 
     conn.Open(); 
     Dump(conn, "after open"); 

     using (var cmd = new SqlCommand()) 
     { 
      cmd.Connection = conn; 
      cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; 
      cmd.ExecuteNonQuery(); 
     } 

     Dump(conn, "after set iso"); 

     using (var cmd = new SqlCommand()) 
     { 
      cmd.Connection = conn; 
      cmd.CommandText = "BEGIN TRANSACTION"; 
      cmd.ExecuteNonQuery(); 
     } 

     Dump(conn, "after sql-based begin transaction"); 

     using (var cmd = new SqlCommand()) 
     { 
      cmd.Connection = conn; 
      cmd.CommandText = "COMMIT"; 
      cmd.ExecuteNonQuery(); 
     } 

     Dump(conn, "after sql-based commit"); 

     var trans = conn.BeginTransaction(); 

     Dump(conn, "after .net begin transaction", trans); 

     trans.Commit(); 

     Dump(conn, "after .net commit"); 
    } 
} 

public static void Dump(SqlConnection connection, string title, SqlTransaction transaction = null) 
{ 
    using (var cmd = new SqlCommand()) 
    { 
     cmd.Connection = connection; 
     if (transaction != null) 
      cmd.Transaction = transaction; 
     cmd.CommandText = "SELECT transaction_isolation_level FROM sys.dm_exec_sessions WHERE session_id = @@SPID"; 
     Debug.WriteLine(title + "=" + Convert.ToInt32(cmd.ExecuteScalar())); 
    } 
} 

Będzie wyjście:

after open=2 
after set iso=1 
after sql-based begin transaction=1 
after sql-based commit=1 
after .net begin transaction=2 
after .net commit=2 

Tutaj można zobaczyć, że ręcznie początek i popełnienie transakcja za pośrednictwem SQL nie zmieniłaby poziomu izolacji, ale zaczynała się a transakcja w .NET bez jawnego stwierdzenia poziomu izolacji nadal zmienia ją na odczyt zatwierdzony.

Ponieważ wszędzie czytamy, rozpoczęcie transakcji bez wyraźnego stwierdzenia poziomu izolacji mówi, że odziedziczyła ona poziom izolacji sesji, domyślam się, że nie zrozumieliśmy, że .NET nie zrobiłby tego samego.

+0

Powinieneś zaakceptować to jako poprawną odpowiedź. – RBarryYoung

Powiązane problemy