當前位置: 妍妍網 > 碼農

.NET 程式碼效能最佳化的總結

2024-02-24碼農

前言

同事發開中遇到了一個程式碼效能最佳化的問題,原本需求是:從一個資料庫中查詢某個表數據,存放到datatable中,然後遍歷datatable,看這些數據在另一個資料庫的表中是否存在,存在的話就要更新,不存在就要插入。

就這個需求本身來說很簡單,但是隨著數據量的增大,之前透過迴圈遍歷的方式就出現了效能問題。

在思索片刻後,給出的建議是分頁查詢和利用事務批次送出。

一、利用資料庫事務批次送出

using (SqlTransaction transaction = targetConnection.BeginTransaction())
{
foreach (DataRow row in dataTable.Rows)
{
if (CheckIfDataExists(targetConnection, transaction, row))
{
UpdateData(targetConnection, transaction, row);
}
else
{
InsertData(targetConnection, transaction, row);
}
}
transaction.Commit();
}
}

下面兩個方法都還可以最佳化,需要接收批次sql語句,所以可以修改成list< SqlCommand>,然後遍歷執行,此處能說明問題即可

privatevoidUpdateData(SqlConnection connection, SqlTransaction transaction, DataRow row)
{
using (SqlCommand command = new SqlCommand("UPDATE YourTable SET YourUpdateStatement WHERE YourCondition", connection, transaction))
{
// Add parameters to your command here, based on your update statement and condition
// command.Parameters.AddWithValue("@ParameterName", row["ColumnName"]);
command.ExecuteNonQuery();
}
}
privatevoidInsertData(SqlConnection connection, SqlTransaction transaction, DataRow row)
{
using (SqlCommand command = new SqlCommand("INSERT INTO YourTable (YourColumns) VALUES (YourValues)", connection, transaction))
{
// Add parameters to your command here, based on your columns and values
// command.Parameters.AddWithValue("@ParameterName", row["ColumnName"]);
command.ExecuteNonQuery();
}
}


看到這裏的時候,大家可以考慮下,以上方案還有什麽最佳化的地方嗎?

當然是有的,如果數據量持續增大,datatable這樣直接載入到記憶體的方式恐怕會成為效能問題點吧,我們得考慮怎麽最佳化才能避免將大數據一次性載入到記憶體,大部份同學第一個想到的就是分頁,這個方案當然是沒有錯,但是還不夠高級,給大家提示一個關鍵字「yield」,或許從聰明的你已經悟到了,接著往下看。

二、流式處理法

什麽是流式處理法呢?

流式處理是一種處理數據的方式,它允許你在數據到達時立即處理,而不是等待所有數據都到達後再處理。

這種方式特別適合處理大量數據,因為它不需要一次性載入所有數據到記憶體中。

在C#中,你可以使用 yield return 關鍵字來建立一個返回 I Enumerable<T> 的方法, 這個方法可以在每次叠代時返回一個元素,而不是一次性返回所有元 素。

這就是一種流式處理的實作方式。

舉個例子:

private IEnumerable<DataRow> GetDataFromSource()
{
using (SqlConnection sourceConnection = new SqlConnection(sourceConnectionString))
{
sourceConnection.Open();
using (SqlCommand command = new SqlCommand("SELECT * FROM YourTable", sourceConnection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
DataTable dataTable = new DataTable();
while (reader.Read())
{
dataTable.LoadDataRow(reader.GetValues(), LoadOption.Upsert);
DataRow row = dataTable.Rows[dataTable.Rows.Count - 1];
yieldreturn row;
dataTable.Clear();
}
}
}
}

在這個範例中,GetDataFromSource方法每次叠代時返回一個DataRow,而不是一次性返回所有DataRow。這樣,你就可以在每次叠代時處理一個DataRow,而不需要一次性載入所有數據到記憶體中。

但是 ,如果你的數據處理需要跨行操作,你可能需要使用其他的方法了,這個就不適用。

掃盲:yield一次只返回一個難道就不會多次存取資料庫了嗎?

在這個範例中,yield return並不會導致多次存取資料庫。實際上,資料庫查詢只執行一次,然後使用SqlDataReader逐行讀取結果。yield return只是在每次叠代時返回一個DataRow,而不是一次性返回所有DataRow。

當你在foreach迴圈中叠代GetDataFromSource()方法時,每次叠代都會從上次停止的地方繼續,直到SqlDataReader讀取完所有行。這意味著,雖然你每次只處理一個DataRow,但資料庫查詢只執行一次。

既然已經看到這裏了,我們可以繼續再考慮下關於效能最佳化,我們還能從哪些方面著手呢?

1、程式碼層面

使用更高效的數據結構和演算法。

使用緩存避免多次資料庫互動

減少不必要的計算和記憶體分配。

利用並列和異步編程提高效能。

使用效能分析工具定位和最佳化瓶頸。

2、Web API方面

使用HTTP緩存減少不必要的請求。

使用Gzip或Brotli壓縮減少響應大小。

使用分頁、排序和過濾減少返回的數據量。

使用GraphQL或OData讓客戶端可以指定需要的數據。

3、資料庫方面

使用索引加速查詢。

使用批次操作減少資料庫互動次數。

使用讀寫分離和資料庫分片提高並行效能。

使用緩存減少資料庫存取。

4、Nginx方面

使用反向代理和負載均衡提高並行效能。

使用緩存減少後端伺服器的負載。

使用Gzip壓縮減少網路傳輸量。

5、CDN方面

使用CDN加速靜態資源的存取。

使用邊緣計算將計算任務靠近使用者。

6、微服務方面

使用服務間的異步通訊減少等待時間。

使用服務的橫向擴充套件提高並行效能。

使用服務的分區設計提高可延伸性。

根據業務需求,考慮使用redis、rabbitmq、mangoDB等等中介軟體

7、其他方面

使用自動擴縮容的雲服務應對流量波動。

使用效能監控和日誌分析工具定位效能問題。

使用容器和Kubernetes等技術提高部署和執行的效率。

使用鏈路追蹤SkyWorking具體檢視哪條鏈路的效能瓶頸

效能最佳化本身就是一個非常龐大的話題,需要具體問題具體分析,總的來說是,平常能用到的就是以上總結的這些點。

技術永遠是為業務服務的,根據不同的業務選擇合適的技術是高級開發者必須要考慮的問題。

轉自:程式設計師不帥哥

連結:cnblogs.com/Mr-Worlf/p/18020445

- EOF -

推薦閱讀 點選標題可跳轉

看完本文有收獲?請轉發分享給更多人

推薦關註「DotNet」,提升.Net技能

點贊和在看就是最大的支持❤️