Устранение временных ошибок подключения в Базе данных SQL и управляемом экземпляре SQL

Область применения: База данных SQL Azure Управляемый экземпляр SQL Azure Azure Synapse Analytics

В статье объясняется, как предотвращать, диагностировать и устранять ошибки подключения и временные ошибки, возникающие при взаимодействии клиентского приложения с Базой данных SQL Azure, управляемым экземпляром SQL Azure и службой Azure Synapse Analytics. Узнайте, как настроить логику повторных попыток, создать строку подключения и настроить другие параметры подключения.

Временные ошибки (временные сбои)

Временные ошибки (или временные сбои) возникают из-за причин, которые вскоре устраняются автоматически. Иногда временная ошибка возникает потому, что система Azure быстро сменяет аппаратные ресурсы, чтобы лучше распределить нагрузку, связанную с разными рабочими нагрузками. Большинство этих событий перенастройки завершаются менее чем за 60 секунд. Во время этой перенастройки могут возникать ошибки, связанные с подключением к вашей базе данных в Базе данных SQL. При разработке приложений, которые подключаются к вашей базе данных, необходимо учитывать возможность появления этих временных ошибок и предусматривать их обработку путем реализации логики повторных попыток в коде вместо вывода пользователю сообщений об ошибках приложения.

Если клиентская программа использует пакет ADO.NET, ваша программа получает исключение SqlException и таким образом узнает о временной ошибке.

Подключение и команда

Повторите попытку подключения к Базе данных SQL и управляемому экземпляру SQL или установите подключение заново, в зависимости от ситуации.

  • Временная ошибка возникает при попытке подключения.

Через несколько секунд повторите попытку подключения.

  • Временная ошибка возникает при выполнении команды, отправляющей запрос к Базе данных SQL и управляемому экземпляру SQL.

Не повторяйте команду сразу. Лучше заново установите подключение после небольшой задержки. Затем снова выполните команду.

Повтор логики для временных ошибок

Клиентские программы, в которых иногда происходят временные ошибки, являются более надежными, если используют логику повторных попыток. Если программа взаимодействует с вашей базой данных в Базе данных SQL через стороннее программное обеспечение промежуточного слоя, спросите поставщика, содержит ли это стороннее ПО логику повторных попыток на случай временных ошибок.

Принципы повторных попыток

  • Если ошибка является временной, попробуйте установить соединение снова.
  • Не повторяйте сразу инструкцию SELECT для Базы данных SQL или управляемого экземпляра SQL, при выполнении которой возникла временная ошибка. Установите новое подключение и повторите SELECT.
  • Если выполнение инструкции UPDATE для Базы данных SQL или управляемого экземпляра SQL завершается временной ошибкой, установите новое подключение, прежде чем выполнить ее еще раз. Логика повторных попыток обеспечивает полноценное выполнение транзакции базы данных или откат всей транзакции.

Другие соображения по поводу повторных попыток

  • Пакетная программа, которая автоматически запускается после окончания рабочего дня и завершает свою работу до наступления утра, может выполнять повторные попытки с продолжительными интервалами.
  • Программа, использующая пользовательский интерфейс, должна учитывать, что человек не любит ждать очень долго. Это не значит, что нужно повторять попытку каждые несколько секунд, так как подобная политика может переполнить систему запросами.

Увеличение интервала между повторными попытками

Рекомендуется подождать 5 секунд, прежде чем выполнять первую повторную попытку. Повторная попытка после ожидания менее 5 секунд может привести к перегрузке облачной службы. Для каждой последующей повторной попытки ожидание должно увеличиваться экспоненциально, но не более чем до 60 секунд.

Период блокировки для клиентов, которые используют ADO.NET, описан в статье Пулы подключений (ADO.NET).

Вы также можете задать максимальное количество повторных попыток, которые программа должна выполнить перед автоматическим завершением работы.

Образцы кода с логикой повторных попыток

Образцы кода с логикой повторных попыток доступны по ссылкам:

Тестирование логики повторных попыток

Чтобы протестировать логику повторных попыток, нужно имитировать или вызвать ошибку, которую можно исправить во время работы программы.

Тестирование с отключением от сети

Один из способов протестировать логику повторных попыток — это отключить клиентский компьютер от сети во время работы программы. Ошибка:

  • SqlException.Number = 11001
  • Сообщение: "Данный узел неизвестен."

В рамках первой повторной попытки вы можете заново подключить свой клиентский компьютер к сети и повторить попытку подключения.

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

  • Временно добавляет 11001 в свой список ошибок, чтобы эта ошибка считалась временной.
  • Первый раз пытается подключиться как обычно.
  • После выявления ошибки удаляет 11001 из списка.
  • Выводит сообщение с просьбой подключить компьютер к сети.
  • Программа приостанавливает дальнейшее выполнение с помощью метода Console.ReadLine или диалогового окна с кнопкой "ОК". Клавишу ВВОД пользователю нужно нажимать после подключения компьютера к сети.
  • Попытаться подключиться еще раз. В этот раз попытка должна завершиться успехом.

Проведите тест, указав при подключении имя пользователя с опечаткой

Программа может намеренно сделать опечатку в имени пользователя перед первой попыткой подключения. Ошибка:

  • SqlException.Number = 18456
  • Сообщение: "Ошибка входа пользователя WRONG_MyUserName."

В рамках первой повторной попытки программа может исправить опечатку, а затем попытаться подключиться.

Чтобы сделать это тестирование практичным, программа должна распознать параметр времени выполнения и сделать следующее:

  • Временно добавить 18456 в свой список ошибок, чтобы эта ошибка считалась временной.
  • Добавить элемент WRONG_ к имени пользователя.
  • После выявления ошибки удалить 18456 из списка.
  • Удалить WRONG_ из имени пользователя.
  • Попытаться подключиться еще раз. В этот раз попытка должна завершиться успехом.

Параметры .NET SqlConnection для повторной попытки подключения

Если клиентская программа подключается к вашей базе данных в Базе данных SQL с помощью класса .NET Framework System.Data.SqlClient.SqlConnection, используйте .NET 4.6.1 или более поздней версии (либо .NET Core), так как в них реализована поддержка повторных попыток подключения. Сведения об этой функции см. в статье Свойство SqlConnection.ConnectionString.

При создании строки подключения для объекта SqlConnection нужно правильно настроить значения следующих параметров.

  • ConnectRetryCount: значение по умолчанию — 1. Диапазон — от 0 до 255.
  • ConnectRetryInterval: значение по умолчанию — 10 секунд. Диапазон — от 1 до 60.
  • Время ожидания подключения: значение по умолчанию — 15 секунд. Диапазон — от 0 до 2147483647.

В частности, выбранные значения должны обеспечивать равенство Connection Timeout = ConnectRetryCount * ConnectionRetryIntervall.

Например, если количество попыток — 3, интервал между ними составляет 10 секунд, а время ожидания подключения только 29 секунд, то у системы не останется времени для последней (третьей) попытки подключения: 29 < 3 * 10.

Подключение и команда

Параметры ConnectRetryCount и ConnectRetryInterval позволяют объекту SqlConnection повторять операцию подключения, не извещая об этом программу и не вынуждая ее управлять этим процессом. Повторные попытки будут выполняться в следующих ситуациях:

  • При вызове метода SqlConnection.Open
  • При вызове метода SqlConnection.Execute

Есть один важный нюанс. Если во время выполнения запроса возникает временная ошибка, объект SqlConnection не пытается подключиться еще раз. Как следствие, запрос повторно не выполняется. Но перед отправкой запроса для выполнения объект SqlConnection очень быстро проверит наличие соединения. Если эта быстрая проверка обнаружит проблемы с подключением, SqlConnection повторит операцию подключения. Если повторная попытка подключения завершится успешно, запрос отправится на выполнение.

Нужно ли сочетать ConnectRetryCount с логикой повторных попыток в приложении?

Предположим, что в вашем приложении реализована надежная настраиваемая логика повторных попыток. Допустим, приложение повторяет операцию подключения четыре раза. Если вы добавите в строку подключения значения ConnectRetryInterval и ConnectRetryCount = 3, общее количество повторных попыток составит 4 * 3 = 12. Вряд ли вам действительно нужно такое количество повторных попыток.

Подключения к вашей базе данных в Базе данных SQL

Подключение: строка подключения

Строка подключения к вашей базе данных, немного отличается от строки для подключения к SQL Server. Строку подключения для своей базы данных вы можете скопировать на портале Azure.

Получение строки подключения на портале Azure

Для получения строки подключения, необходимой для взаимодействия клиентской программы с Базой данных SQL Azure, используйте портал Azure.

  1. Щелкните Все службы>Базы данных SQL.

  2. Введите имя базы данных в текстовое поле фильтра рядом с верхней левой частью колонки Базы данных SQL.

  3. Выберите строку со своей базой данных.

  4. Для удобства после появления колонки для базы данных нажмите кнопку Свернуть, чтобы свернуть колонки, используемые для просмотра и фильтрации базы данных.

  5. В колонке для базы данных выберите Показать строки подключения к базе данных.

  6. Скопируйте нужную строку подключения. т.e. Если вы планируете использовать библиотеку подключений ADO.NET, скопируйте соответствующую строку из вкладки ADO.NET.

    Копирование строки подключения ADO для базы данных

  7. При необходимости исправьте информацию в строке подключения. т.e. Вставьте пароль в строку подключения или удалите "@<servername>" в имени пользователя, если имя пользователя или имя сервера слишком длинные.

  8. Вставьте строку подключения в код клиентской программы в одном или другом формате.

Дополнительные сведения см. в описании строк подключения и файлов конфигурации.

Подключение: IP-адрес

Чтобы принимать подключение от IP-адреса компьютера, на котором установлено клиентское приложение, нужно настроить Базу данных SQL. Чтобы настроить эту конфигурацию, измените параметры брандмауэра с помощью портала Azure.

Если не задать IP-адрес, произойдет сбой программы и отобразится сообщение об ошибке, содержащее необходимый IP-адрес.

  1. Войдите на портал Azure.

  2. В меню слева выберите пункт Все службы.

  3. Найдите и выберите Серверы SQL Server.

    Поиск своего сервера Базы данных SQL Azure на портале

  4. В текстовом поле фильтра начните вводить имя своего сервера. Отобразится строка.

  5. Выберите строку со своим сервером. Отобразится колонка для вашего сервера.

  6. В колонке вашего сервера выберите Параметры.

  7. Выберите Брандмауэр.

    Выбор брандмауэра > параметров

  8. Выберите Добавить IP-адрес клиента. В первое текстовое поле введите имя нового правила.

  9. Введите нижнее и верхнее значения IP-адресов для требуемого диапазона.

    • Удобными могут оказаться нижнее значение, оканчивающееся на .0, и верхнее, оканчивающееся на .255.
  10. Щелкните Сохранить.

См. статью о настройке параметров брандмауэра в Базе данных SQL.

Подключение: порты

Обычно нужно убедиться лишь в том, что на компьютере, на котором установлено клиентское приложение, для исходящей связи открыт порт 1433.

Например, если ваша клиентская программа размещена на компьютере Windows, можно использовать брандмауэр Windows на этом узле, чтобы открыть порт 1433.

  1. Откройте панель управления.
  2. Выберите Все элементы панели управления>Брандмауэр Windows>Дополнительные параметры>Правила для исходящих подключений>Действия>Новое правило.

Если клиентская программа находится на виртуальной машине Azure, см. статью Порты для ADO.NET 4.5, отличные от порта 1433.

Сведения о конфигурации портов и IP-адресов в вашей базе данных см. в статье о брандмауэре базы данных SQL Azure.

Подключение: ADO.NET 4.6.2 или более поздней версии

Если для подключения к Базе данных SQL программа применяет классы ADO.NET, например System.Data.SqlClient.SqlConnection, мы рекомендуем использовать .NET Framework 4.6.2 или более позднюю версию.

Начиная с ADO.NET 4.6.2:

  • Попытки повторного подключения для Azure SQL выполняются немедленно, чтобы повысить производительность приложений с поддержкой облака.

Начиная с ADO.NET 4.6.1:

  • Предлагает повышенную надежность подключения к базам данных SQL с помощью метода SqlConnection.Open. В методе Open теперь реализованы лучшие механизмы повторных попыток при временных сбоях, в частности для некоторых ошибок, которые возникают при ожидании соединения.
  • Поддерживается работа с пулами подключений, а также эффективная проверка функционирования объекта соединения, который видит ваша программа.

Когда вы используете объект соединения, входящий в пул подключений, программе следует временно закрывать подключение, если она его сейчас не использует. Повторное открытие подключения не требует высоких затрат, нужно просто создать новое подключение.

Если вы используете пакет ADO.NET 4.0 или более раннюю версию, мы рекомендуем обновить его до последней версии. С августа 2018 года доступна версия ADO.NET 4.6.2.

Диагностика

Диагностика: проверяет, могут ли служебные программы подключаться

Если программе не удается подключиться к вашей базе данных в Базе данных SQL, для диагностики можно попытаться подключиться с помощью служебной программы. В идеале служебная программа подключается с помощью библиотеки, которую использует ваша программа.

На компьютерах под управлением Windows можно использовать следующие служебные программы:

  • SQL Server Management Studio (ssms.exe) подключается с помощью ADO.NET.
  • sqlcmd.exe подключается с помощью ODBC.

После подключения программы проверьте, работает ли короткий SQL-запрос SELECT.

Диагностика: проверка открытых портов

Если есть подозрение, что попытки подключения не удались из-за проблем с портом, запустите на компьютере служебную программу, которая сообщает о конфигурациях порта.

На компьютерах Linux можно использовать следующие служебные программы:

  • netstat -nap
  • nmap -sS -O 127.0.0.1 — вместо указанного в примере IP-адреса укажите свой.

В Windows можно использовать служебную программу PortQry.exe. Вот пример запуска с ноутбука, в рамках которого запрашивались сведения о ситуации с портами в вашей БД в Базе данных SQL:

[C:\Users\johndoe\]
>> portqry.exe -n johndoesvr9.database.windows.net -p tcp -e 1433

Querying target system called: johndoesvr9.database.windows.net

Attempting to resolve name to IP address...
Name resolved to 23.100.117.95

querying...
TCP port 1433 (ms-sql-s service): LISTENING

[C:\Users\johndoe\]
>>

Диагностика: внесение ошибок в журнал

Эпизодические неисправности иногда лучше всего диагностировать, выявляя общий шаблон за несколько дней или недель подряд.

Клиент может помочь в диагностике. Для этого ему следует вносить в журнал все ошибки, с которыми он сталкивается. У вас может получиться коррелировать записи журнала со сведениями об ошибках, которые база данных SQL вносит в журнал для внутренних целей.

Для облегчения ведения журналов можно использовать Enterprise Library 6 (EntLib60), где используются классы .NET. Дополнительные сведения см. в статье 5 - As Easy As Falling Off a Log: Using the Logging Application Block (5. Простой вариант: использование решения Logging Application Block).

Диагностика: проверка системных журналов на наличие ошибок

Ниже приведены некоторые Transact-SQL-инструкции SELECT, которые запрашивают в журналах сведения об ошибках и прочую информацию.

Запрос у журнала Описание
SELECT e.*
FROM sys.event_log AS e
WHERE e.database_name = 'myDbName'
AND e.event_category = 'connectivity'
AND 2 >= DateDiff
  (hour, e.end_time, GetUtcDate())
ORDER BY e.event_category,
  e.event_type, e.end_time;
В представлении sys.event_log приводятся сведения об отдельных событиях, включая те, которые могут привести к временным ошибкам или проблемам с подключением.

В идеале значения start_time или end_time можно сопоставить с временем возникновения ошибок в клиентской программе.

Для выполнения этого запроса необходимо подключиться к базе данных master.
SELECT c.*
FROM sys.database_connection_stats AS c
WHERE c.database_name = 'myDbName'
AND 24 >= DateDiff
  (hour, c.end_time, GetUtcDate())
ORDER BY c.end_time;
Представление sys.database_connection_stats отображает суммарное количество событий каждого типа, что также бывает полезно при дополнительной диагностике.

Для выполнения этого запроса необходимо подключиться к базе данных master.

Диагностика: поиск событий проблемы в журнале базы данных SQL

Искать записи о событиях проблемы можно в журнале базы данных SQL. Попробуйте выполнить в базе данных master такую Transact-SQL-инструкцию SELECT:

SELECT
   object_name
  ,CAST(f.event_data as XML).value
      ('(/event/@timestamp)[1]', 'datetime2')                      AS [timestamp]
  ,CAST(f.event_data as XML).value
      ('(/event/data[@name="error"]/value)[1]', 'int')             AS [error]
  ,CAST(f.event_data as XML).value
      ('(/event/data[@name="state"]/value)[1]', 'int')             AS [state]
  ,CAST(f.event_data as XML).value
      ('(/event/data[@name="is_success"]/value)[1]', 'bit')        AS [is_success]
  ,CAST(f.event_data as XML).value
      ('(/event/data[@name="database_name"]/value)[1]', 'sysname') AS [database_name]
FROM
  sys.fn_xe_telemetry_blob_target_read_file('el', null, null, null) AS f
WHERE
  object_name != 'login_event'  -- Login events are numerous.
  and
  '2015-06-21' < CAST(f.event_data as XML).value
        ('(/event/@timestamp)[1]', 'datetime2')
ORDER BY
  [timestamp] DESC
;

Несколько возвращенных строк из sys.fn_xe_telemetry_blob_target_read_file

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

object_name                   timestamp                    error  state  is_success  database_name

database_xml_deadlock_report  2015-10-16 20:28:01.0090000  NULL   NULL   NULL        AdventureWorks

Enterprise Library 6

Enterprise Library 6 (EntLib60) — это библиотека классов .NET, которая помогает реализовывать надежные клиенты для облачных служб, одной из которых является База данных SQL. Дополнительные сведения обо всех полезных возможностях EntLib60 вы найдете в этой статье.

Одна из областей, в которой может помочь EntLib60, — логика повторных попыток для обработки временных ошибок. Дополнительные сведения см. в статье 4 - Perseverance, Secret of All Triumphs: Using the Transient Fault Handling Application Block (4. Настойчивость — секрет всех побед. Использование блока приложения для обработки временных ошибок).

Примечание

Исходный код для EntLib60 доступен для открытого скачивания в Центре загрузки. Корпорация Майкрософт не планирует обновлять функции и менять характер обслуживания библиотеки EntLib.

Классы EntLib60 для временных ошибок и повторов

Следующие классы EntLib60 особенно полезны для логики повторных ошибок. Все эти классы входят в пространство имен Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling или во вложенные пространства.

В пространстве имен Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling:

  • RetryPolicy ;
    • ExecuteAction ;
  • ExponentialBackoff ;
  • SqlDatabaseTransientErrorDetectionStrategy ;
  • ReliableSqlConnection ;
    • ExecuteCommand ;

В пространстве имен Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.TestSupport:

  • AlwaysTransientErrorDetectionStrategy ;
  • NeverTransientErrorDetectionStrategy ;

Ниже приведены некоторые ссылки на сведения об EntLib60.

EntLib60: блок ведения журнала

  • Блок ведения журнала — это очень гибкое и настраиваемое решение, которое позволяет выполнять такие действия:
    • создавать и хранить сообщения журнала в разных расположениях;
    • классифицировать и фильтровать сообщения;
    • собирать контекстную информацию, полезную для отладки, трассировки, аудита и выполнения общих требований к ведению журнала.
  • Это решение извлекает функции ведения журнала из расположения журнала, что обеспечивает согласованность кода приложения независимо от расположения и типа хранилища журнала.

Дополнительные сведения см. в статье 5 - As Easy As Falling Off a Log: Using the Logging Application Block (5. Простой вариант: использование решения Logging Application Block).

Исходный код метода EntLib60 IsTransient

Ниже приведен исходный код (на языке C#) метода IsTransient из класса SqlDatabaseTransientErrorDetectionStrategy. Исходный код поясняет, какие ошибки считаются временными и приемлемыми для повторной попытки (версия за апрель 2013 г.).

public bool IsTransient(Exception ex)
{
  if (ex != null)
  {
    SqlException sqlException;
    if ((sqlException = ex as SqlException) != null)
    {
      // Enumerate through all errors found in the exception.
      foreach (SqlError err in sqlException.Errors)
      {
        switch (err.Number)
        {
            // SQL Error Code: 40501
            // The service is currently busy. Retry the request after 10 seconds.
            // Code: (reason code to be decoded).
          case ThrottlingCondition.ThrottlingErrorNumber:
            // Decode the reason code from the error message to
            // determine the grounds for throttling.
            var condition = ThrottlingCondition.FromError(err);

            // Attach the decoded values as additional attributes to
            // the original SQL exception.
            sqlException.Data[condition.ThrottlingMode.GetType().Name] =
              condition.ThrottlingMode.ToString();
            sqlException.Data[condition.GetType().Name] = condition;

            return true;

          case 10928:
          case 10929:
          case 10053:
          case 10054:
          case 10060:
          case 40197:
          case 40540:
          case 40613:
          case 40143:
          case 233:
          case 64:
            // DBNETLIB Error Code: 20
            // The instance of SQL Server you attempted to connect to
            // does not support encryption.
          case (int)ProcessNetLibErrorCode.EncryptionNotSupported:
            return true;
        }
      }
    }
    else if (ex is TimeoutException)
    {
      return true;
    }
    else
    {
      EntityException entityException;
      if ((entityException = ex as EntityException) != null)
      {
        return this.IsTransient(entityException.InnerException);
      }
    }
  }

  return false;
}

Дальнейшие действия

См. также раздел