針對 SQL Database 和 SQL 受控執行個體中的暫時性連線錯誤進行疑難排解

適用於:Azure SQL DatabaseAzure SQL 受控執行個體Azure Synapse Analytics

本文描述如何防止、疑難排解、診斷和減少您的用戶端應用程式在與 Azure SQL Database、Azure SQL 受控執行個體和 Azure Synapse Analytics 互動時發生的連線錯誤和暫時性錯誤。 了解如何設定重試邏輯、建置連接字串和調整其他連接設定。

暫時性錯誤 (暫時性故障)

暫時性錯誤 (也稱為暫時性故障) 具有很快就會自行解決的根本原因。 當 Azure 系統快速地將硬體資源轉移到負載平衡更好的各種工作負載時,偶爾會發生暫時性錯誤。 其中大部分重新設定事件會在不到 60 秒內完成。 在此重新設定時間範圍期間,您可能會在 SQL Database 中連線到資料庫時發生問題。 連線到您資料庫的應用程式,都預期會發生這些暫時性錯誤。 若要處理這些錯誤,請在其程式碼中實作重試邏輯,而不是對使用者呈現為應用程式錯誤。

如果用戶端程式使用 ADO.NET,系統會擲回 SqlException,告知您的程式發生暫時性錯誤。

連線與命令

請重試 SQL Database 和 SQL 受控執行個體連線,或根據下列各項重新建立:

  • 嘗試連線期間發生暫時性錯誤

在延遲數秒後重試連線。

  • 在 SQL Database 和 SQL 受控執行個體查詢命令期間發生暫時性錯誤

請勿立即重試命令。 而是在延遲之後,建立全新的連線。 然後重試此命令。

暫時性錯誤的重試邏輯

用戶端程式若包含重試邏輯,在偶爾遇到暫時性錯誤時就會更可靠。 當您的程式透過第三方中介軟體與 SQL Database 中的資料庫進行通訊時,請詢問廠商中介軟體是否包含暫時性錯誤的重試邏輯。

重試原則

  • 如果是暫時性錯誤,請重試開啟連線。
  • 請勿直接重試因為暫時性錯誤而失敗的 SQL Database 或 SQL 受控執行個體 SELECT 陳述式。 您應建立全新的連線,然後重試 SELECT
  • 當 SQL Database 或 SQL 受控執行個體 UPDATE 陳述式由於暫時性錯誤而失敗時,請先建立全新的連線,再重試 UPDATE。 重試邏輯必須確保整個資料庫交易完成,或整個交易已復原。

其他重試考量

  • 在下班後自動啟動並在早上前完成的批次程式,可以進行長時間間隔的重試。
  • 使用者介面程式應該說明了人類將在長時間等候後放棄的傾向。 此方案不得每隔幾秒鐘重試,因為該原則可能讓系統充斥要求。

增加重試之間的間隔

我們建議您在您第一次重試前等待 5 秒鐘。 在少於 5 秒的延遲後重試,雲端服務會有超過負荷的風險。 對於後續每次重試,延遲應以指數方式成長,最大值為 60 秒。

如需使用 ADO.NET 的用戶端封鎖期間討論,請參閱連線集區 (ADO.NET)

您也可以設定在程式自行終止之前的重試次數上限。

具有重試邏輯的程式碼範例

使用重試邏輯的程式碼範例位於:

測試您的重試邏輯

若要測試您的重試邏輯,您必須在程式仍在執行時模擬或產生可以更正的錯誤。

中斷與網路連接以進行測試

您可以測試重試邏輯的方法,就是在程式執行時中斷用戶端電腦與網路的連接。 錯誤是:

  • SqlException.Number = 11001
  • 訊息:「未知的主機」

在第一次重試的過程中,您可以將用戶端電腦重新連線到網路,然後嘗試連線。

若要使這項測試可行,請先中斷電腦的網路連線,再啟動您的程式。 然後,您的程式會辨識一個執行階段參數,以便程式:

  • 暫時將 11001 加入至其錯誤清單,視為暫時性。
  • 如往常般嘗試其第一個連接。
  • 在攔截到錯誤之後,請從清單中移除 11001。
  • 顯示一則訊息,告訴使用者將電腦連到網路。
  • 使用 Console.ReadLine 方法或含 [確定] 按鈕的對話方塊,暫停進一步執行。 使用者在電腦連上網路之後按下 Enter 鍵。
  • 重新嘗試連接,預期成功。

連線時拼錯使用者名稱以進行測試

在第一次連接嘗試之前,您的程式可以故意拼錯使用者名稱。 錯誤是:

  • SqlException.Number = 18456
  • 錯誤將為:「使用者 'WRONG_MyUserName' 登入失敗。」

第一次重試時,您的程式可以更正拼字錯誤,然後嘗試連線。

若要使這項測試可行,您的程式會辨識一個執行階段參數,以便程式:

  • 暫時將 18456 加入至其錯誤清單,視為暫時性。
  • 故意將 'WRONG_' 加入至使用者名稱。
  • 在攔截到錯誤之後,請從清單中移除 18456。
  • 從使用者名稱中移除 'WRONG_'。
  • 重新嘗試連接,預期成功。

進行連線重試的.NET SqlConnection 參數

如果用戶端程式利用 .NET Framework 類別 System.Data.SqlClient.SqlConnection 連線至 Azure SQL Database 中的資料庫,請使用 .NET 4.6.1 或更新版本 (或 .NET Core),以便您使用其連線重試功能。 如需此功能的詳細資訊,請參閱 SqlConnection.ConnectionString 屬性

當您為 SqlConnection 物件建立連接字串時,請調整下列參數的值:

  • ConnectRetryCount:預設值為 1。 範圍是 0255
  • ConnectRetryInterval:預設值為 10 秒。 範圍是 160
  • Connection Timeout:預設值為 15 秒。 範圍是 02147483647
  • Command Timeout:預設值為 30 秒。 範圍是 02147483647

連線重試設定 (ConnectRetryCountConnectRetryInterval) 適用於連線復原。 連線復原包含以下不同型別:

  • 開始連線復原是指初始的 SqlConnection.OpenOpenAsync() 方法。 第一次連線嘗試會計算為第零次重試。 ConnectRetryCount 適用於後續重試。 因此,如果第零次連線失敗 (這可能不會立即發生),會先套用 ConnectRetryInterval,接著再套用後續的 ConnectRetryCount (和 ConnectRetryInterval) 嘗試。 若要利用所有的重試嘗試,Connection Timeout 屬性必須提供時間進行所有的嘗試。

  • 閒置連線復原是指自動偵測並重新連線中斷的現有閒置連線。 第一次嘗試重新連線中斷的閒置連線會計算為第一次重試嘗試。 若要利用所有的重試嘗試,Command Timeout 必須提供時間進行所有的嘗試。

範例:假設 ConnectRetryCountConnectRetryInterval 參數使用下列值:

ConnectRetryCount:3ConnectRetryInterval:10 秒

了解如何在下列案例中使用這些值:

案例:新連線

4:10:00 - Connection.Open() - 第零次嘗試

4:10:01 - 偵測到連線失敗

4:10:11 - 第 1 次重試 -->ConnectRetryInterval 之後發生第一次重試

4:10:21 - 第 2 次重試

4:10:31 - 第 3 次重試

若是此案例,您選擇的值應滿足下列條件:
Connection Timeout > = ConnectRetryCount * ConnectionRetryInterval

例如,如果計數是 3 且間隔是 10 秒,則僅 29 秒的逾時無法讓系統有足夠的時間進行第三次即最後一次的連線重試:

29 < 3 * 10

案例:閒置連線

ConnectRetryCount:3ConnectRetryInterval:10 秒

4:10:00 - 在執行命令時偵測到中斷的連線

4:10:00 - 第 1 次重試 >立即發生第一次重試

4:10:10 - 第 2 次重試

4:10:20 - 第 3 次重試

這不是初始的連線。 因此,不會套用 Connection Timeout。 不過,因為連線復原在執行命令期間發生,所以會套用 Command Timeout 設定。 Connection Timeout 預設值為 30 秒。 雖然在一般情況下,連線復原速度很快,但間歇性中斷可能會導致復原需要一些執行命令的時間。

在此案例中,如果您想要充分利用閒置連線復原的重試,您選擇的值應滿足下列條件:
Command Timeout > (ConnectRetryCount - 1) * ConnectionRetryInterval

例如,如果計數是 3 且間隔是 10 秒,則低於 20 秒的 Command Timeout 值無法提供足夠的時間進行第三次即最後一次的連線重試:(3 - 1) * 10 = 20`

此外,請考慮命令本身需要時間以在復原連線之後執行。

注意

這些案例中提供的持續時間值僅供示範之用。 兩種案例中的實際偵測時間取決於基礎結構。

連線與命令

ConnectRetryCountConnectRetryInterval 參數讓您的 SqlConnection 物件可重試連接作業,而不需告知或中斷您的程式,例如將控制權傳回您的程式。 可能會在以下情況中發生重試:

  • SqlConnection.Open 方法呼叫
  • SqlConnection.Execute 方法呼叫

有一些微妙的差異。 若在執行「查詢」 時發生暫時性錯誤,您的 SqlConnection 物件並不會重試連線作業。 而且絕對不會重試查詢。 不過,在傳送您的查詢以供執行之前, SqlConnection 會先快速檢查連接。 如果快速檢查偵測到連接問題, SqlConnection 會重試連接作業。 如果重試成功,就會傳送您的查詢以供執行。

是否應該將 ConnectRetryCount 與應用程式重試邏輯結合

假設您的應用程式有健全的自訂重試邏輯。 它可能會重試連線作業 4 次。 如果您將 ConnectRetryIntervalConnectRetryCount =3 加入到連接字串,您會將重試次數增加為 4 * 3 = 12 次重試。 您可能不會想要這麼多的重試次數。

在 SQL Database 中連線到您的資料庫

連接:連接字串

連線到您的資料庫所需的連接字串,與用於連線到 SQL Server 的字串稍有不同。 您可以從 Azure 入口網站複製資料庫的連接字串。

從 Azure 入口網站取得連接字串

使用 Azure 入口網站來取得用戶端程式與 Azure SQL Database 進行互動所需的連接字串。

  1. 選取 [所有服務]>[SQL 資料庫]

  2. 在 [SQL 資料庫] 窗格左上角附近的篩選文字輸入框中輸入您的資料庫名稱。

  3. 選取資料庫的資料列。

  4. 在窗格顯示您的資料庫之後,為了閱讀方便,選取 [最小化] 按鈕來摺疊用於瀏覽和資料庫篩選的窗格。

  5. 在您資料庫的窗格上,選取 [顯示資料庫連接字串]

  6. 複製適當的連接字串。 也就是說,如果您想要使用 ADO.NET 連線庫,請從 [ADO.NET] 索引標籤複製適當的字串。

    Copy the ADO connection string for your database

  7. 視需要編輯該連接字串。 也就是說,將您的密碼插入連接字串,或是在使用者名稱或伺服器名稱太長的情況下,從使用者名稱移除 "@<servername>"。

  8. 以其中一種格式,將連接字串資訊貼入您的用戶端程式碼中。

如需詳細資訊,請參閱連接字串與設定檔

連接:IP 位址

您必須設定 SQL Database,以接受來自裝載您用戶端程式之電腦的通訊。 若要設定此組態,請透過 Azure 入口網站編輯防火牆設定。

如果您忘了設定 IP 位址,您的程式會失敗並出現一個好用的錯誤訊息,陳述必要的 IP 位址。

  1. 登入 Azure 入口網站

  2. 在左側的清單中,選取 [所有服務]

  3. 捲動並選取 [SQL Server]

    Find your Azure SQL Database server in the portal

  4. 在篩選文字方塊中開始輸入您的伺服器名稱。 即會顯示您的資料列。

  5. 選取伺服器的資料列。 即會顯示您伺服器的窗格。

  6. 在伺服器窗格中,選取 [設定]

  7. 選取 [防火牆]

    Select Settings > Firewall

  8. 選取 [新增用戶端 IP]。 在第一個文字方塊中替新規則輸入名稱。

  9. 輸入低和高 IP 位址值作為要啟用的範圍。

    • 將低值結尾設為 .0、高值結尾設為 .255 會比較方便。
  10. 選取 [儲存]。

如需詳細資訊,請參閱在 SQL Database 中進行防火牆設定

連接:連接埠

通常,您只需要在裝載用戶端程式的電腦上確定已開啟連接埠 1433 進行輸出通訊。

例如,當用戶端程式裝載在 Windows 電腦上時,您可在主機上使用 Windows 防火牆來開啟通訊埠 1433。

  1. 開啟 [控制台]。
  2. 選取 [所有控制面板項目]>[Windows 防火牆]>[進階設定]>[輸出規則]>[動作]>[新規則]

如果您的用戶端程式裝載在 Azure 虛擬機器 (VM) 上,請閱讀適用於 ADO.NET 4.5 和 SQL Database 的 1433 以外的連接埠

如需在您的資料庫中設定連接埠及 IP 位址的背景資訊,請參閱 Azure SQL Database 防火牆

連線:ADO.NET 4.6.2 或更新版本

如果您的程式使用 System.Data.SqlClient.SqlConnection 之類的 ADO.NET 類別來連線到 SQL Database,建議您使用 .NET Framework 4.6.2 版或更新版本。

開頭是 ADO.NET 4.6.2

  • 要立即針對 Azure SQL 重試的連線開啟嘗試,因而提升雲端式應用程式的效能。

開頭是 ADO.NET 4.6.1

  • 對於 SQL Database,使用 SqlConnection.Open 方法來開啟連線時,可靠性更高。 針對連線逾時期間內的特定錯誤,Open 方法現在包含最佳重試機制來因應暫時性錯誤。
  • 支援連線集區,包括其提供給您的程式的連線物件是否能運作的有效驗證。

當您使用連線集區中的連線物件時,建議您的程式若未立即使用連線,則暫時將它關閉。 重新開啟連線並不會耗費很多資源,但是會建立新的連線。

如果您使用 ADO.NET 4.0 或更舊版本,建議您升級到最新的 ADO.NET。 從 2018 年 8 月起,您可以下載 ADO.NET 4.6.2

診斷

診斷:測試公用程式是否可以連接

如果您的程式無法連線到 SQL Database 中的資料庫,有一個診斷選項可嘗試透過公用程式連線。 在理想的情況下,此公用程式會使用您的程式使用的同一程式庫進行連線。

您可以在任何 Windows 電腦上,嘗試這些公用程式:

  • SQL Server Management Studio (SSMS.exe),其使用 ADO.NET 進行連線
  • sqlcmd.exe,使用 sqlcmd.exe 進行連線

在您的程式連線之後,請測試簡短的 SQL SELECT 查詢是否能運作。

診斷:檢查開啟的連接埠

如果您懷疑連線嘗試因為連接埠問題而失敗,您可以在報告連接埠組態的電腦上執行公用程式。

下列公用程式在 Linux 上可能很有用:

  • netstat -nap
  • nmap -sS -O 127.0.0.1:將範例值變更為您的 IP 位址。

在 Windows 上,PortQry.exe 公用程式可能很有用。 以下是在 SQL Database 中的資料庫上查詢連接埠情況,以及在膝上型電腦上執行的範例執行:

[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 Database 本身內部記錄的錯誤資料相互關聯。

Enterprise Library 6 (EntLib60) 提供 .NET 受控類別來協助記錄。 如需詳細資訊,請參閱 5 - 輕而易舉:使用記錄應用程式區塊

診斷:檢查系統記錄找出錯誤

以下是一些可查詢錯誤記錄和其他資訊的 Transact-SQL SELECT 陳述式。

記錄查詢 Description
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_timeend_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 Database 記錄中搜尋問題事件

您可以在 SQL Database 記錄中搜尋有關問題事件的項目。 在 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 傳回的資料列

下列範例顯示傳回的資料列可能的樣子。 顯示的 null 值通常在其他資料列不是 null。

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 Database。 若要找出 EntLib60 所能協助之每個領域的專用主題,請參閱 Enterprise Library 6 - 2013 年 4 月

在 EntLib60 可以協助的一個領域中用於處理暫時性錯誤的重試邏輯。 如需詳細資訊,請參閱 4 - 堅持是所有成功的秘方:使用暫時性錯誤處理應用程式區塊

注意

EntLib60 的原始程式碼可從下載中心公開下載。 Microsoft 沒有計劃進一步更新或維護 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 - 輕而易舉:使用記錄應用程式區塊

EntLib60 IsTransient 方法的原始程式碼

接下來,IsTransient 方法的 C# 原始程式碼來自 SqlDatabaseTransientErrorDetectionStrategy 類別。 原始程式碼會釐清哪些錯誤會被視為暫時性並值得重試 (從 2013 年 4 月起)。

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;
}

後續步驟