目錄
一、代碼解析:
二、解決方案
1、增加日志記錄
2、異步操作
注意事項
3、增加超時機制
4、使用線程池
5、使用信號量或事件
6、監控數據庫連接狀態
在C#程序操作Sql Server數據庫的實際應用中,若異常就會拋出異常,我們還能找到異常的原因,進一步去解決;但偶發的不返回、也不拋出異常的情況真是令人頭疼,下面我將從C#程序代碼層面去分析解決思路,先上代碼
/// <summary>/// 執行存儲過程,返回影響的行數/// </summary>/// <param name="procedureName">存儲過程名</param>/// <param name="parameters">自定義參數數組,根據調用數據庫的種類,給參數中相應的數組賦值</param>/// <returns></returns>public int ExecuteProcedure(string procedureName, object[] aParas){SqlCommand cmd = new SqlCommand();try{PrepareCommand(con, cmd, null, CommandType.StoredProcedure, procedureName, aParas);int rows = cmd.ExecuteNonQuery();cmd.Parameters.Clear();return rows;}catch (Exception err){throw err;}}
一、代碼解析:
我們解析下這個函數,這個也是一個比較簡單常用的方法:
這個函數的作用是執行一個存儲過程,并返回該存儲過程執行后影響的行數。下面是對函數各個部分的詳細解釋:
函數定義
/// <summary>
/// 執行存儲過程,返回影響的行數
/// </summary>
/// <param name="procedureName">存儲過程名</param>
/// <param name="parameters">自定義參數數組,根據調用數據庫的種類,給參數中相應的數組賦值</param>
/// <returns></returns>
public int ExecuteProcedure(string procedureName, object[] aParas)
- summary: 該函數用于執行存儲過程并返回影響的行數。
- param:?
procedureName
?是存儲過程的名稱。 - param:?
parameters
?是一個自定義參數數組,根據調用數據庫的種類,給參數中相應的數組賦值。 - returns: 返回存儲過程執行后影響的行數。
函數實現
public int ExecuteProcedure(string procedureName, object[] aParas)
{//創建SqlCommand對象SqlCommand cmd = new SqlCommand();try{//調用PrepareCommand方法來設置SqlCommand對象的參數,包括連接對象con、命令對象cmd、命令類型(這里是存儲過程)、存儲過程名稱和參數數組。PrepareCommand(con, cmd, null, CommandType.StoredProcedure, procedureName, aParas);//使用ExecuteNonQuery方法執行存儲過程,并返回影響的行數。int rows = cmd.ExecuteNonQuery();//清除SqlCommand對象的參數,以便下次使用。cmd.Parameters.Clear();return rows;}catch (Exception err){//捕獲并拋出任何可能發生的異常。throw err;}
}
????????這個函數的主要作用是執行一個存儲過程,并返回該存儲過程執行后影響的行數。它通過設置SqlCommand
對象的參數,執行存儲過程,并處理可能發生的異常。
二、解決方案
? ? ? ? 問題:在實際應用中遇到這種情況,即在調用 ExecuteProcedure
方法后,沒有到達 return rows;
語句,也沒有進入 catch (Exception err)
塊,這意味著代碼在某個地方被阻塞或掛起,但沒有拋出異常。這可能是由于以下幾種情況之一:
-
死鎖或資源爭用:
- 數據庫連接可能被其他操作占用,導致當前操作無法繼續執行。
- 數據庫中的某個存儲過程可能存在死鎖,導致執行被阻塞。
-
無限循環或長時間運行的操作:
- 存儲過程中可能存在無限循環或長時間運行的操作,導致執行時間過長。
-
網絡問題:
- 數據庫服務器或網絡連接可能出現問題,導致請求無法完成。
-
線程問題:
- 當前線程可能被其他線程或操作阻塞,導致無法繼續執行。
-
內存或資源耗盡:
- 系統內存或資源可能耗盡,導致無法繼續執行。
解決方法
-
增加日志記錄:
- 在關鍵步驟增加更多的日志記錄,以便更好地了解代碼執行到哪個步驟。
-
檢查數據庫狀態:
- 檢查數據庫服務器的狀態,查看是否有死鎖或其他問題。
- 使用數據庫管理工具監控數據庫性能和資源使用情況。
-
優化存儲過程:
- 檢查存儲過程的邏輯,確保沒有無限循環或長時間運行的操作。
- 優化存儲過程的性能,減少資源消耗。
-
檢查網絡連接:
- 確保數據庫服務器和應用程序之間的網絡連接穩定。
-
監控系統資源:
- 監控系統內存、CPU 和其他資源的使用情況,確保沒有資源耗盡的情況。
-
使用異步操作:
- 考慮將數據庫操作改為異步操作,以避免阻塞主線程。
????????因為問題是偶發的,半年一年出現一次,也找不出什么原因,但是客戶會較真,拿這個說事;以上方法,從代碼層面只有1、6適用 ,當然我們也可以通過控制超時 時間來處理,實用解決方案如下:
1、增加日志記錄
?關鍵步驟增加日志記錄,以便更好地了解代碼執行情況:
public int ExecuteProcedure(string procedureName, object[] aParas)
{SqlCommand cmd = new SqlCommand();try{// 增加日志記錄Log("Preparing command for procedure: " + procedureName);PrepareCommand(con, cmd, null, CommandType.StoredProcedure, procedureName, aParas);// 增加日志記錄Log("Executing command for procedure: " + procedureName);int rows = cmd.ExecuteNonQuery();// 增加日志記錄Log("Command executed successfully, rows affected: " + rows);cmd.Parameters.Clear();return rows;}catch (Exception err){// 增加日志記錄Log("Exception occurred: " + err.Message);throw err;}
}private void Log(string message)
{// 實現日志記錄邏輯,例如寫入文件或數據庫Console.WriteLine(message);
}
????????通過增加日志記錄,可以更好地了解代碼執行到哪個步驟,從而更容易定位問題,這種特殊情況也只能定位到走哪一行就沒往下走了。
2、異步操作
將數據庫操作改為異步操作可以提高應用程序的響應性和性能,特別是在處理大量數據或長時間運行的操作時。
使用?ExecuteNonQueryAsync
?方法
SqlCommand
類提供了 ExecuteNonQueryAsync
方法,用于異步執行 SQL 命令。以下是修改后的 ExecuteProcedure
方法:
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;public async Task<int> ExecuteProcedureAsync(string procedureName, object[] aParas)
{SqlCommand cmd = new SqlCommand();try{// 增加日志記錄Log("Preparing command for procedure: " + procedureName);PrepareCommand(con, cmd, null, CommandType.StoredProcedure, procedureName, aParas);// 異步執行命令Log("Executing command for procedure: " + procedureName);int rows = await cmd.ExecuteNonQueryAsync();// 增加日志記錄Log("Command executed successfully, rows affected: " + rows);cmd.Parameters.Clear();return rows;}catch (Exception err){// 增加日志記錄Log("Exception occurred: " + err.Message);throw err;}
}private void Log(string message)
{// 實現日志記錄邏輯,例如寫入文件或數據庫Console.WriteLine(message);
}
調用異步方法
在調用異步方法時,需要使用 await
關鍵字,并且調用方法也必須是異步的。例如:
public async Task SomeMethodAsync()
{string procedureName = "YourStoredProcedureName";object[] parameters = new object[] { /* 參數值 */ };try{int rowsAffected = await ExecuteProcedureAsync(procedureName, parameters);Console.WriteLine("Rows affected: " + rowsAffected);}catch (Exception ex){Console.WriteLine("Exception: " + ex.Message);}
}
注意事項
- 異步方法的返回類型:異步方法的返回類型通常是?
Task
?或?Task<T>
,其中?T
?是方法的返回類型。 - 異步上下文:確保在異步上下文中調用異步方法,例如在?
async
?方法中使用?await
。 - 異常處理:異步方法中的異常處理與同步方法類似,但需要使用?
await
?關鍵字捕獲異常。
????????通過將數據庫操作改為異步操作,可以提高應用程序的性能和響應性,特別是在處理大量數據或長時間運行的操作時。
??????但是并不是所有程序都適合異步操作,那么將如何處理呢?
如果程序邏輯不能使用異步方式,但仍然遇到在調用 ExecuteProcedure
方法后沒有到達 return rows;
語句的問題,可以考慮以下幾種方法來解決這個問題:
3、增加超時機制
為數據庫操作增加超時機制,確保操作不會無限期地阻塞。可以使用 SqlCommand
的 CommandTimeout
屬性來設置超時時間。
public int ExecuteProcedure(string procedureName, object[] aParas)
{SqlCommand cmd = new SqlCommand();try{// 設置命令超時時間為30秒cmd.CommandTimeout = 30;PrepareCommand(con, cmd, null, CommandType.StoredProcedure, procedureName, aParas);int rows = cmd.ExecuteNonQuery();cmd.Parameters.Clear();return rows;}catch (Exception err){throw err;}
}
????????這個超時機制在特殊情況下沒有用,親測沒用,就算不設置CommandTimeout屬性的值,也有默認的吧,特殊情況等多久都沒返回沒異常。
4、使用線程池
將數據庫操作放在單獨的線程中執行,并在主線程中等待結果。可以使用 ThreadPool
或 Task
來實現這一點。
using System.Threading;public int ExecuteProcedure(string procedureName, object[] aParas)
{int rows = 0;bool isCompleted = false;Exception exception = null;ThreadPool.QueueUserWorkItem(_ =>{SqlCommand cmd = new SqlCommand();try{PrepareCommand(con, cmd, null, CommandType.StoredProcedure, procedureName, aParas);rows = cmd.ExecuteNonQuery();cmd.Parameters.Clear();}catch (Exception err){exception = err;}finally{isCompleted = true;}});// 等待操作完成或超時DateTime startTime = DateTime.Now;while (!isCompleted && (DateTime.Now - startTime).TotalSeconds < 30){Thread.Sleep(100);}if (exception != null){throw exception;}if (!isCompleted){throw new TimeoutException("Database operation timed out.");}return rows;
}
5、使用信號量或事件
使用信號量或事件來同步主線程和數據庫操作線程。
using System.Threading;public int ExecuteProcedure(string procedureName, object[] aParas)
{int rows = 0;Exception exception = null;ManualResetEvent completionEvent = new ManualResetEvent(false);ThreadPool.QueueUserWorkItem(_ =>{SqlCommand cmd = new SqlCommand();try{PrepareCommand(con, cmd, null, CommandType.StoredProcedure, procedureName, aParas);rows = cmd.ExecuteNonQuery();cmd.Parameters.Clear();}catch (Exception err){exception = err;}finally{completionEvent.Set();}});// 等待操作完成或超時if (!completionEvent.WaitOne(TimeSpan.FromSeconds(30))){throw new TimeoutException("Database operation timed out.");}if (exception != null){throw exception;}return rows;
}
6、監控數據庫連接狀態
在執行數據庫操作之前,檢查數據庫連接的狀態,確保連接是可用的。
public int ExecuteProcedure(string procedureName, object[] aParas)
{SqlCommand cmd = new SqlCommand();try{// 檢查數據庫連接狀態if (con.State != ConnectionState.Open){con.Open();}PrepareCommand(con, cmd, null, CommandType.StoredProcedure, procedureName, aParas);int rows = cmd.ExecuteNonQuery();cmd.Parameters.Clear();return rows;}catch (Exception err){throw err;}
}
????????
通過以上方法,可以在不改變程序邏輯的情況下,增加超時機制、使用線程池、信號量或事件來解決數據庫操作阻塞的問題。這些方法可以幫助確保數據庫操作在合理的時間內完成,并在出現異常時及時處理。