Як вирішити проблему пулу з'єднань між ASP.NET та SQL Server?


211

Останні кілька днів ми занадто сильно бачимо це повідомлення про помилку на нашому веб-сайті:

"Час очікування минув. Період очікування закінчився до отримання з'єднання з пулу. Це може статися тому, що використовуються всі об'єднані з'єднання та досягнуто максимального розміру пулу."

Ми нічого не змінили в нашому коді. Я переглянув код, щоб перевірити відкриті з'єднання, які не закрилися, але виявив, що все добре.

  • Як я можу це вирішити?

  • Чи потрібно мені редагувати цей пул?

  • Як я можу редагувати максимальну кількість з'єднань цього пулу?

  • Яке рекомендоване значення для веб-сайту з високим трафіком?


Оновлення:

Чи потрібно щось редагувати в IIS?

Оновлення:

Я виявив, що кількість активних з'єднань становить від 15 до 31, і я виявив, що максимальна дозволена кількість з'єднань, налаштованих на SQL-сервері, становить понад 3200 підключень, 31 занадто багато, або я повинен щось редагувати в конфіграції ASP.NET ?


Максимальний розмір пулу - 100, якщо я правильно пам’ятаю. Більшість веб-сайтів не використовують більше 50 з’єднань під великим навантаженням - залежить від часу, на який потрібно виконати ваші запити. Короткочасне виправлення в рядку підключення: спробуйте встановити більш високе значення у рядках підключення: "Максимальний розмір пулу = ..."
splattne

скільки? зробити, наприклад, 200?
Amr Elgarhy

3
Я думаю, що вам слід справді шукати те, що викликає проблему. Ваші запити (або деякі з них) працюють дуже довго?
splattne

Можливо, це справжня причина, запит, який потребує багато часу для виконання, я буду шукати в цьому, спасибі
Amr Elgarhy

1
Сподіваюся, ви зможете знайти проблему. Пропозиція: якщо ви використовуєте SQL Server, спробуйте "SQL Profiler" і шукайте довгі запити: sql-server-performance.com/articles/per/…
splattne

Відповіді:


218

У більшості випадків проблеми об’єднання підключень пов'язані з "витоками з'єднання". Ваша програма, ймовірно, не закриває підключення до бази даних правильно та послідовно. Якщо ви залишаєте з'єднання відкритими, вони залишаються заблокованими, поки збирач сміття .NET не закриє їх для вас, викликаючи їх Finalize()метод.

Ви хочете переконатися, що ви дійсно закриваєте зв’язок . Наприклад, наступний код призведе до витоку з'єднання, якщо код між .Openі Closeвидає виняток:

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

Правильним було б таке:

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

або

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

Коли ваша функція повертає з'єднання з методу класу, переконайтесь, що ви кешуєте його локально та називаєте його Closeметод. Ви підключите з'єднання за допомогою цього коду, наприклад:

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

З'єднання, повернене з першого дзвінка на getConnection(), не закривається. Замість того, щоб закрити зв’язок, ця лінія створює нову та намагається її закрити.

Якщо ви користуєтесь SqlDataReaderабо a OleDbDataReader, закрийте їх. Навіть незважаючи на те, що закриття з’єднання саме по собі здається виправданням, докладіть додаткових зусиль, щоб закрити об'єкти зчитувача даних, коли ви їх використовуєте.


Ця стаття " Чому переповнення басейну підключення? " З журналу MSDN / SQL пояснює багато деталей та пропонує деякі стратегії налагодження:

  • Виконати sp_whoабо sp_who2. Ці системні збережені процедури повертають інформацію із sysprocessesсистемної таблиці, яка показує стан та інформацію про всі робочі процеси. Як правило, ви побачите один ідентифікатор серверного процесу (SPID) за з'єднання. Якщо ви назвали своє з'єднання, використовуючи аргумент Ім'я програми в рядку з'єднання, ваші робочі з'єднання легко знайти.
  • Використовуйте Профілер SQL Server із TSQL_Replayшаблоном SQLProfiler для відстеження відкритих з'єднань. Якщо ви знайомі з Profiler, цей спосіб простіше, ніж опитування, використовуючи sp_who.
  • Використовуйте Монітор ефективності для контролю пулів та з'єднань. Я обговорюю цей метод за мить.
  • Монітор лічильників продуктивності в коді. Ви можете стежити за станом вашого пулу з'єднань та кількістю встановлених з'єднань, використовуючи підпрограми для вилучення лічильників або за допомогою нових елементів керування .NET PerformanceCounter.

4
Невелика корекція: GC ніколи не викликає метод розпорядження об'єкта, а лише його фіналізатор (якщо такий існує). Потім фіналізатор може зробити "резервний" виклик Dispose, якщо це необхідно, хоча я не впевнений, чи так це робить SqlConnection.
ЛукаХ

1
Чи є якась погіршення продуктивності, коли нам потрібно встановити максимальний розмір пулу 50, і у нас є лише кілька користувачів.
mahesh sharma

37

Після встановлення .NET Framework v4.6.1 наші з'єднання з віддаленою базою даних негайно почали вимикати час через цю зміну .

Для виправлення просто додайте параметр TransparentNetworkIPResolutionу рядок з'єднання та встановіть його false :

Сервер = myServerName; База даних = myDataBase; Trusted_Connection = True; TransparentNetworkIPResolution = Неправильне


У цьому випадку проблема - "Період очікування, який минув до отримання з'єднання з пулу". Чи вирішено це за допомогою рядка підключення, чи це була окрема проблема рукостискання?
FBryant87

1
Як я пам’ятаю, це було саме таке повідомлення про помилку, як і в питанні. Це сталося одразу після оновлення до .NET Framework v4.6.1.
ajbeaven

Це стосувалося і мене, виправили це для мене в службі додатків, яку я працював на Azure, підключення до бази даних Azure SQL. Я використовував Dapper і правильно розпоряджався з'єднаннями, але все ще отримав повідомлення про помилку "проміжок часу, який минув до отримання з'єднання з пулу". Але не більше, тому дякую @ajbeaven
Стів Кеннард

13

Якщо ваше використання значно не подорожчало, мабуть, є лише відставання в роботі. IMO, найімовірніший варіант - це те, що щось використовує з'єднання і не відпускає їх негайно. Ви впевнені , що використовуєте їх usingу всіх випадках? Або (через якийсь механізм) звільнення з'єднань?


13

Ви перевіряли наявність DataReaders, які не закриті, і довідки Response.redirects перед тим, як закрити з'єднання або читчик даних. З'єднання залишаються відкритими, коли ви не закриєте їх перед переадресацією.


3
+1 - Або функції, що повертають DataReaders - З'єднання ніколи не закриється за межами функції, яку ви створили ...
splattne

1
Якщо ваша функція повертає SqlDataReader, вам краще перетворити її на DataTable, ніж збільшити максимальний розмір пулу
live-love

10

З цією проблемою ми час від часу стикаємося і на нашому веб-сайті. Винуватець у нашому випадку - застаріла статистика / індекси. Це призводить до того, що попередньо швидко запущений запит (зрештою) стає повільним і вичерпується.

Спробуйте оновити статистику та / або відновити індекси на таблицях, на які впливає запит, і подивіться, чи це допомагає.


3
Я думаю, що це пояснить, чому запит може закінчитися, але я не думаю, що це пояснить, чому виникає тайм-аут, намагаючись отримати з'єднання.
DrGriff

1
Можливо, при поганому індексі запити займають більше часу, і одночасно використовується більше з'єднань.
LosManos

1
Працював для мене після оновлення всіх статистичних даних за допомогою sp_updatestats на db: EXEC sp_updatestats;
boateng

6

Ви можете вказати мінімальний та максимальний розмір пулу, вказавши MinPoolSize=xyzта / або MaxPoolSize=xyzв рядку з'єднання. Однак ця проблема може бути іншою річчю.


3
Що рекомендується MaxPoolSize?
Amr Elgarhy

2
Напевно, найкращий спосіб - це не вказати його, якщо у вас немає спеціальних вимог і ви не знаєте хороший розмір пулу для вашого конкретного випадку .
Мехрдад Афшарі

1
Примітка: я перевірив і виявив, що активних підключень до мого db є близько 22 живих, це занадто багато?
Amr Elgarhy

Я не думаю, що так. Стандартний розмір пулу - 100 підключень, я думаю. Це залежить від завантаження кожного з'єднання в мережі та SQL-сервері. Якщо вони виконують важкі запити, це може спричинити проблеми. Також проблеми з мережею можуть виникати під час ініціювання нового з'єднання і можуть спричинити це виняток.
Мехрдад Афшарі

5

Я також зіткнувся з цією проблемою, коли використовував якийсь сторонній рівень даних в одному з моїх програм .NET. Проблема полягала в тому, що шар не закрив з'єднання належним чином.

Ми викинули шар і самі створили один, який завжди закриває і розпоряджається зв’язками. Відтоді ми більше не отримуємо помилки.


Ми використовуємо LLBL, і веб-сайт працює від 2 років, і саме останні кілька днів почали діяти так.
Amr Elgarhy

5

Ви можете спробувати це і для вирішення проблеми з очікуванням часу:

Якщо ви не додали httpRuntime у веб-конфіг, додайте його в <system.web>тег

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

і

Змініть рядок з'єднання таким чином;

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

Останнє використання

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

3

В основному це пов’язано з тим, що з'єднання не було закрито в додатку. Використовуйте "MinPoolSize" та "MaxPoolSize" у рядку з'єднання.


3

У моєму випадку я не закривав об’єкт 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

Якщо ви працюєте над складним застарілим кодом, де просте використання (..) {..} неможливе - як я було - ви можете перевірити фрагмент коду, який я розмістив у цьому запитанні SO, щоб визначити спосіб стек виклику створення з'єднання, коли з'єднання потенційно протікає (не закривається після встановленого тайм-ауту). Це дозволяє досить легко виявити причину витоків.


2

Не інстанціонуйте з'єднання sql занадто багато разів. Відкрийте одне або два з'єднання та використовуйте їх для всіх наступних операцій sql.

Здається, що навіть під Disposeчас з'єднання викид викидається.


2

Окрім розміщених рішень….

У роботі з 1000 сторінками застарілого коду, кожен з яких викликає загальний GetRS кілька разів, ось ще один спосіб виправити проблему:

У існуючій загальній DLL ми додали параметр 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);   
    }

Потім на кожній сторінці, поки ви закриєте зчитувач даних, з'єднання також автоматично закривається, щоб уникнути витоків з'єднання.

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

2

У мене просто була така ж проблема і я хотів поділитися тим, що допомогло мені знайти джерело: Додайте ім'я програми до рядка підключення, а потім наберіть відкрите підключення до SQL Server

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

Ця проблема була в моєму коді. Я вставлю якийсь приклад коду, над яким я потрапив нижче помилки. Період очікування минув до отримання з'єднання з пулу. Це могло статися тому, що використовувалися всі об'єднані з'єднання та досягнуто максимального розміру пулу.

 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();**

Ви хочете щоразу закривати з'єднання. До цього я не мав нам тісного зв'язку через це я отримав помилку. Після додавання близького твердження у мене з’явилася ця помилка


0

Ви протікали підключення до свого коду. Ви можете спробувати використовувати, щоб підтвердити, що ви їх закриваєте.

 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

З цією проблемою я стикався раніше. Це зрештою було проблемою з брандмауером. Я щойно додав правило до брандмауера. Мені довелося відкрити порт, 1433щоб сервер SQL міг підключитися до сервера.


0

Використовуй це:

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

12
Можливо, я пропускаю суть SqlConnection.ClearPool, але лайк, який просто перешкоджає поверненню поточного з'єднання до пулу з'єднань? Я думав, що ідея пулу з'єднань полягає в тому, щоб дозволити швидше з'єднання. Безумовно, вивільняючи з'єднання з пулом кожного разу, коли воно закінчується, значить, потрібно створити НОВЕ підключення КОЖНОГО ВРЕМЯ, а не витягнути запасний з пулу? Поясніть, будь ласка, як і чому ця методика корисна.
Діб

0

Так, є спосіб змінити конфігурацію. Якщо ви перебуваєте на спеціальному сервері і просто потребуєте більше підключень SQL, ви можете оновити записи "максимальний розмір пулу" в обох рядках з'єднання, дотримуючись цих інструкцій:

  1. Увійдіть у свій сервер за допомогою віддаленого робочого столу
  2. Відкрийте Мій комп'ютер (Windows - E) та перейдіть до C: \ inetpub \ vhosts [домен] \ httpdocs
  3. Двічі клацніть на файлі web.config. Це може бути просто вказано як веб, якщо структура файлу встановлена ​​для приховування розширень. Це відкриє редактор Visual Basic або подібний редактор.
  4. Знайдіть свої підключення: вони будуть схожі на приклади нижче:

    "add name =" SiteSqlServer "connectionString =" сервер = (локальний); база даних = dbname; uid = dbuser; pwd = dbpassword; pooling = true; тривалість з'єднання = 120; максимальний розмір пулу = 25; ""

5.Змініть максимальний розмір пулу = значення X на необхідний розмір пулу.

  1. Збережіть і закрийте файл web.config.

0

Переконайтеся, що ви встановили правильні настройки для пулу з'єднань. Це дуже важливо, як я пояснив у наступній статті: https://medium.com/@dewanwaqas/configurations-that-significantly-improves-your-app-performance-built-using-sql-server-and-net- ed044e53b60 Ви побачите різке поліпшення продуктивності програми, якщо будете дотримуватися цього.


0

У моєму випадку у мене був нескінченний цикл (із get Property, що намагається отримати значення з бази даних), який постійно відкривав сотні з'єднань Sql.

Щоб відтворити проблему, спробуйте:

while (true)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();
        someCall(connection);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.