Jak rozwiązać problem z pulą połączeń między ASP.NET a SQL Server?


211

W ciągu ostatnich kilku dni zbyt często widzimy ten komunikat o błędzie w naszej witrynie:

„Upłynął limit czasu. Limit czasu upłynął przed uzyskaniem połączenia z puli. Mogło to nastąpić, ponieważ wszystkie połączenia w puli były w użyciu i osiągnięto maksymalny rozmiar puli.”

Od dłuższego czasu nic nie zmieniliśmy w naszym kodzie. Poprawiłem kod, aby sprawdzić otwarte połączenia, które się nie zamknęły, ale stwierdziłem, że wszystko jest w porządku.

  • Jak mogę to rozwiązać?

  • Czy muszę edytować tę pulę?

  • Jak mogę edytować maksymalną liczbę połączeń w tej puli?

  • Jaka jest zalecana wartość witryny o dużym natężeniu ruchu?


Aktualizacja:

Czy muszę coś edytować w IIS?

Aktualizacja:

Przekonałem się, że liczba aktywnych połączeń wynosi od 15 do 31, i odkryłem, że maksymalna dozwolona liczba połączeń skonfigurowana na serwerze SQL to ponad 3200 połączeń, jest o 31 za dużo lub powinienem coś edytować w konfiguracji ASP.NET ?


Domyślnie maksymalny rozmiar puli wynosi 100, jeśli dobrze pamiętam. Większość stron internetowych nie używa więcej niż 50 połączeń pod dużym obciążeniem - zależy od tego, jak długo trwa wypełnianie zapytań. Krótkoterminowa poprawka w parametrze Connection Connection: spróbuj ustawić wyższą wartość w parametrach połączenia: „Max Pool Size = ...”
splattne

ile? zrobić na przykład 200?
Amr Elgarhy

3
Myślę, że naprawdę powinieneś poszukać przyczyny problemu. Czy Twoje zapytania (lub niektóre z nich) działają bardzo długo?
splattne

może to jest prawdziwy powód, zapytanie, którego wykonanie zajmuje dużo czasu, przeszukam to, dzięki
Amr Elgarhy

1
Mam nadzieję, że znajdziesz problem. Sugestia: jeśli używasz programu SQL Server, wypróbuj „SQL Profiler” i poszukaj długich zapytań: sql-server-performance.com/articles/per/…
splattne

Odpowiedzi:


218

W większości przypadków problemy z pulowaniem połączeń są związane z „przeciekami połączeń”. Twoja aplikacja prawdopodobnie nie zamyka poprawnie i konsekwentnie połączeń z bazą danych. Po pozostawieniu otwartych połączeń pozostają one zablokowane, dopóki moduł do usuwania śmieci .NET nie zamknie ich za Ciebie, wywołując ich Finalize()metodę.

Chcesz się upewnić, że naprawdę zamykasz połączenie . Na przykład następujący kod spowoduje wyciek połączenia, jeśli kod pomiędzy .Openi Closezgłasza wyjątek:

var connection = new SqlConnection(connectionString);
connection.Open();
// some code
connection.Close();                

Prawidłowy sposób byłby następujący:

var connection = new SqlConnection(ConnectionString);
try
{
     connection.Open();
     someCall (connection);
}
finally
{
     connection.Close();                
}

lub

using (SqlConnection connection = new SqlConnection(connectionString))
{
     connection.Open();
     someCall(connection);
}

Gdy funkcja zwróci połączenie z metody klasy, pamiętaj, aby buforować ją lokalnie i wywołać jej Closemetodę. Wyciekniesz z połączenia za pomocą tego kodu, na przykład:

var command = new OleDbCommand(someUpdateQuery, getConnection());
result = command.ExecuteNonQuery();
connection().Close(); 

Połączenie zwrócone z pierwszego połączenia do getConnection()nie jest zamykane. Zamiast zamykać połączenie, ta linia tworzy nowe i próbuje je zamknąć.

Jeśli używasz SqlDataReaderlub a OleDbDataReader, zamknij je. Chociaż wydaje się, że zamknięcie samego połączenia załatwia sprawę, włóż dodatkowy wysiłek, aby jawnie zamknąć obiekty czytnika danych podczas ich używania.


W artykule „ Dlaczego przepełnienie puli połączeń? ” Z magazynu MSDN / SQL Magazine wyjaśnia wiele szczegółów i sugeruje niektóre strategie debugowania:

  • Uruchom sp_wholub sp_who2. Te systemowe procedury składowane zwracają informacje z sysprocessestabeli systemowej, która pokazuje status i informacje o wszystkich działających procesach. Zasadniczo zobaczysz jeden identyfikator procesu serwera (SPID) na połączenie. Jeśli nazwa połączenia została nazwana przy użyciu argumentu Nazwa aplikacji w ciągu połączenia, można łatwo znaleźć działające połączenia.
  • Użyj SQL Server Profiler z TSQL_Replayszablonem SQLProfiler do śledzenia otwartych połączeń. Jeśli znasz program Profiler, ta metoda jest łatwiejsza niż odpytywanie za pomocą sp_who.
  • Użyj Monitora wydajności do monitorowania pul i połączeń. Omówię tę metodę za chwilę.
  • Monitoruj liczniki wydajności w kodzie. Możesz monitorować kondycję swojej puli połączeń i liczbę ustanowionych połączeń za pomocą procedur do wyodrębnienia liczników lub za pomocą nowych kontrolek .NET PerformanceCounter.

4
Mała korekta: GC nigdy nie wywołuje metody Dispose obiektu, tylko jego finalizator (jeśli taki istnieje). Finalizator może następnie wykonać „rezerwowe” wywołanie Dispose, jeśli to konieczne, chociaż nie jestem pewien, czy SqlConnection to robi.
Łukasza

1
Czy istnieje jakikolwiek spadek wydajności, gdy musimy ustawić maksymalny rozmiar puli 50 i mamy tylko niewielu użytkowników.
mahesh sharma

37

Po zainstalowaniu .NET Framework wer. 4.1.1 nasze połączenia ze zdalną bazą danych natychmiast zaczęły wygasać z powodu tej zmiany .

Aby to naprawić, po prostu dodaj parametr TransparentNetworkIPResolutiondo ciągu połączenia i ustaw go na false :

Serwer = mojaSerwerNazwa; Baza danych = mojaDataBase; Trusted_Connection = True; TransparentNetworkIPResolution = False


W takim przypadku problemem jest „Limit czasu, który upłynął przed uzyskaniem połączenia z puli”. Czy Twój ciąg połączenia naprawił to, czy był to osobny problem z uzgadnianiem?
FBryant87

1
Z tego, co pamiętam, był to dokładnie taki sam komunikat o błędzie jak w pytaniu. Wystąpiło to natychmiast po aktualizacji do .NET Framework v4.6.1.
ajbeaven

Tak też było w przypadku mnie, naprawiłem to w usłudze aplikacji, którą uruchomiłem na platformie Azure, połączenie z bazą danych Azure SQL. Korzystałem z Dappera i poprawnie pozbywałem się połączeń, ale wciąż pojawia się komunikat o błędzie „Upłynął limit czasu przed uzyskaniem połączenia z puli”. Ale nie więcej, więc dzięki @ajbeaven
Steve Kennaird

13

Jeśli twoje użycie nie wzrosło, wydaje się mało prawdopodobne, aby zaległa praca. IMO, najbardziej prawdopodobną opcją jest to, że coś korzysta z połączeń i nie zwalnia ich natychmiast. Czy na pewno używasz usingwe wszystkich przypadkach? Lub (poprzez dowolny mechanizm) zwalniając połączenia?


13

Czy sprawdziłeś, czy czytniki danych nie są zamknięte i czy odpowiedz.redirects przed zamknięciem połączenia lub centrum danych? Połączenia pozostają otwarte, jeśli nie zamkniesz ich przed przekierowaniem.


3
+1 - Lub funkcje zwracające DataReaders - Połączenie nigdy nie zostanie zamknięte poza funkcją, którą je utworzyłeś ...
splattne

1
Jeśli funkcja zwraca SqlDataReader, lepiej jest przekonwertować ją na DataTable, niż zwiększyć maksymalny rozmiar puli
live-love

10

Od czasu do czasu napotykamy ten problem również na naszej stronie internetowej. Winowajcą w naszym przypadku są nieaktualne statystyki / indeksy. Powoduje to, że wcześniej szybko działające zapytanie (ostatecznie) staje się wolne i kończy się limit czasu.

Spróbuj zaktualizować statystyki i / lub odbudować indeksy w tabelach objętych zapytaniem i sprawdź, czy to pomoże.


3
Myślę, że to wyjaśniałoby, dlaczego zapytanie może przekroczyć limit czasu, ale nie sądzę, że wyjaśniałoby to, dlaczego upłynął limit czasu podczas próby uzyskania połączenia.
DrGriff

1
Może się zdarzyć, że przy złym indeksie zapytania będą trwać dłużej, a jednocześnie używanych będzie więcej połączeń.
LosManos

1
Pracowałem dla mnie po zaktualizowaniu wszystkich statystyk za pomocą sp_updatestats na db: EXEC sp_updatestats;
boateng

6

Możesz określić minimalny i maksymalny rozmiar puli, określając MinPoolSize=xyzi / lub MaxPoolSize=xyzw ciągu połączenia. Ta przyczyna problemu może być jednak inna.


3
Jaki jest zalecany MaxPoolSize?
Amr Elgarhy

2
Prawdopodobnie najlepszym sposobem jest nie sprecyzowanie go, chyba że masz specjalne wymagania i znasz dobry rozmiar puli dla konkretnego przypadku .
Mehrdad Afshari

1
Uwaga: sprawdziłem i stwierdziłem, że aktywne połączenia z moją bazą danych to około 22 na żywo, czy to za dużo?
Amr Elgarhy

Nie wydaje mi się Domyślny rozmiar puli to 100 połączeń. To zależy od obciążenia każdego połączenia w sieci i na serwerze SQL. Jeśli prowadzą one duże zapytania, może to powodować problemy. Ponadto podczas inicjowania nowego połączenia mogą wystąpić problemy z siecią i mogą powodować ten wyjątek.
Mehrdad Afshari

5

Ten problem również napotkałem, gdy korzystałem z warstwy danych innej firmy w jednej z moich aplikacji .NET. Problem polegał na tym, że warstwa nie zamykała poprawnie połączeń.

Wyrzuciliśmy warstwę i sami ją stworzyliśmy, która zawsze zamyka i usuwa połączenia. Od tego czasu nie otrzymujemy już błędu.


Korzystamy z LLBL, a strona działa od 2 lat i tak właśnie zaczęło działać kilka ostatnich dni.
Amr Elgarhy

5

Możesz także tego spróbować, aby rozwiązać problem z przekroczeniem limitu czasu:

Jeśli nie dodałeś httpRuntime do webconfig, dodaj to w <system.web>tagu

<sytem.web>
     <httpRuntime maxRequestLength="20000" executionTimeout="999999"/>
</system.web>

i

Zmodyfikuj parametry połączenia w ten sposób;

 <add name="connstring" connectionString="Data Source=DSourceName;Initial Catalog=DBName;Integrated Security=True;Max Pool Size=50000;Pooling=True;" providerName="System.Data.SqlClient" />

Przy ostatnim użyciu

    try
    {...} 
    catch
    {...} 
    finaly
    {
     connection.close();
    }

3

Wynika to głównie z faktu, że połączenie nie zostało zamknięte w aplikacji. Użyj „MinPoolSize” i „MaxPoolSize” w ciągu połączenia.


3

W moim przypadku nie zamykałem obiektu DataReader.

        using (SqlCommand dbCmd = new SqlCommand("*StoredProcedureName*"))
        using (dbCmd.Connection = new SqlConnection(WebConfigurationAccess.ConnectionString))
            {
            dbCmd.CommandType = CommandType.StoredProcedure;

            //Add parametres
            dbCmd.Parameters.Add(new SqlParameter("@ID", SqlDbType.Int)).Value = ID;
.....
.....
            dbCmd.Connection.Open();
            var dr = dbCmd.ExecuteReader(); //created a Data reader here
            dr.Close();    //gotta close the data reader
            //dbCmd.Connection.Close(); //don't need this as 'using' statement should take care of this in its implicit dispose method.
            }

2

Jeśli pracujesz nad złożonym starszym kodem, w którym proste użycie (..) {..} nie jest możliwe - tak jak ja - możesz zajrzeć do fragmentu kodu, który opublikowałem w tym pytaniu SO, w celu ustalenia sposobu stos wywołań tworzenia połączenia, gdy połączenie jest potencjalnie wyciekiem (nie jest zamykane po upływie określonego czasu). Ułatwia to wykrycie przyczyny wycieków.


2

Nie twórz zbyt często połączenia SQL. Otwórz jedno lub dwa połączenia i użyj ich do wszystkich następnych operacji SQL.

Wydaje się, że nawet podczas Disposeinicjowania połączeń zgłaszany jest wyjątek.


2

Oprócz opublikowanych rozwiązań ....

W przypadku 1000 stron starszego kodu, z których każdy wywołuje wspólny GetRS wiele razy, oto inny sposób na rozwiązanie tego problemu:

W istniejącej wspólnej bibliotece DLL dodaliśmy opcję CommandBehavior.CloseConnection :

    static public IDataReader GetRS(String Sql)
    {
        SqlConnection dbconn = new SqlConnection(DB.GetDBConn());
        dbconn.Open();
        SqlCommand cmd = new SqlCommand(Sql, dbconn);
        return cmd.ExecuteReader(CommandBehavior.CloseConnection);   
    }

Następnie na każdej stronie, o ile zamykasz czytnik danych, połączenie jest również automatycznie zamykane, aby zapobiec wyciekowi połączenia.

    IDataReader rs = CommonDLL.GetRS("select * from table");
    while (rs.Read())
    {
        // do something
    }
    rs.Close();   // this also closes the connection

2

Właśnie miałem ten sam problem i chciałem podzielić się tym, co pomogło mi znaleźć źródło: dodaj nazwę aplikacji do ciągu połączenia, a następnie zainicjuj otwarte połączenie z serwerem SQL

select st.text,
    es.*, 
    ec.*
from sys.dm_exec_sessions as es
    inner join sys.dm_exec_connections as ec on es.session_id = ec.session_id
    cross apply sys.dm_exec_sql_text(ec.most_recent_sql_handle) st
where es.program_name = '<your app name here>'

1

Ten problem miałem w kodzie. Wkleję przykładowy kod, który przekroczyłem, poniżej błędu. Limit czasu upłynął przed uzyskaniem połączenia z puli. Może się tak zdarzyć, ponieważ wszystkie połączenia w puli były w użyciu i osiągnięto maksymalny rozmiar puli.

 String query = "insert into STATION2(ID,CITY,STATE,LAT_N,LONG_W) values('" + a1 + "','" + b1 + "','" + c1 + "','" + d1 + "','" + f1 + "')";
    //,'" + d1 + "','" + f1 + "','" + g1 + "'

    SqlConnection con = new SqlConnection(mycon);
    con.Open();
    SqlCommand cmd = new SqlCommand();
    cmd.CommandText = query;
    cmd.Connection = con;
    cmd.ExecuteNonQuery();
    **con.Close();**

Chcesz zamknąć połączenie za każdym razem. Wcześniej nie miałem bliskiego połączenia z tego powodu, dostałem błąd. Po dodaniu instrukcji close nadszedł ten błąd


0

Wyciekałeś z połączeń w swoim kodzie. Możesz spróbować użyć przy użyciu do potwierdzenia, że ​​je zamykasz.

 Using (SqlConnection sqlconnection1 = new SqlConnection(“Server=.\\SQLEXPRESS ;Integrated security=sspi;connection timeout=5”)) {
                          sqlconnection1.Open();
                          SqlCommand sqlcommand1 = sqlconnection1.CreateCommand();
                          sqlcommand1.CommandText = raiserror (‘This is a fake exception’, 17,1)”;
                          sqlcommand1.ExecuteNonQuery();  //this throws a SqlException every time it is called.
                          sqlconnection1.Close(); //Still never gets called.
              } // Here sqlconnection1.Dispose is _guaranteed_

https://blogs.msdn.microsoft.com/angelsb/2004/08/25/connection-pooling-and-the-timeout-expired-exception-faq/


0

Ten problem, z którym się wcześniej spotkałem. Skończyło się to problemem z zaporą ogniową. Właśnie dodałem regułę do zapory. Musiałem otworzyć port, 1433aby serwer SQL mógł połączyć się z serwerem.


0

Użyj tego:

finally
{
    connection.Close();
    connection.Dispose();
    SqlConnection.ClearPool();
}

12
Może brakuje mi sensu SqlConnection.ClearPool, ale czy to po prostu zapobiega zwalnianiu twojego aktualnego połączenia z powrotem do puli połączeń? Myślałem, że idea puli połączeń polega na umożliwieniu szybszych połączeń. Z pewnością uwalnianie połączenia z puli za każdym razem, gdy jest ono zakończone, oznacza, że ​​NOWYCH połączeń trzeba będzie tworzyć KAŻDYM raz, zamiast wyciągać zapasowe z puli? Proszę wyjaśnić, w jaki sposób i dlaczego ta technika jest przydatna.
Dib

0

Tak, istnieje sposób na zmianę konfiguracji. Jeśli korzystasz z serwera dedykowanego i potrzebujesz więcej połączeń SQL, możesz zaktualizować wpisy „maksymalnego rozmiaru puli” w obu ciągach połączeń, postępując zgodnie z tymi instrukcjami:

  1. Zaloguj się do serwera za pomocą pulpitu zdalnego
  2. Otwórz Mój komputer (Windows - E) i przejdź do C: \ inetpub \ vhosts [domena] \ httpdocs
  3. Kliknij dwukrotnie plik web.config. Może to być po prostu lista internetowa, jeśli struktura plików jest ustawiona na ukrywanie rozszerzeń. Otworzy się edytor Visual Basic lub podobny.
  4. Znajdź parametry połączenia, będą one wyglądać podobnie do poniższych przykładów:

    "add name =" SiteSqlServer "connectionString =" server = (lokalny); baza danych = nazwa_db; uid = dbuser; pwd = dbpassword; pooling = true; czas życia połączenia = 120; maksymalny rozmiar puli = 25; ""

5.Zmień maksymalny rozmiar puli = wartość X na wymagany rozmiar puli.

  1. Zapisz i zamknij plik web.config.


0

W moim przypadku miałem nieskończoną pętlę (z właściwości Get próbującej uzyskać wartość z bazy danych), która ciągle otwierała setki połączeń Sql.

Aby odtworzyć problem, spróbuj:

while (true)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();
        someCall(connection);
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.