今天同事發開中遇到了一個代碼性能優化的問題,原本需求是:從一個資料庫中查詢某個表數據,存放到datatable中,然後遍歷datatable,看這些數據在另一個資料庫的表中是否存在,存在的話就要更新,不存在就要插入。 就這個需求本身來說很簡單,但是隨著數據量的增大,之前通過迴圈遍歷的方式就出現了性能 ...
今天同事發開中遇到了一個代碼性能優化的問題,原本需求是:從一個資料庫中查詢某個表數據,存放到datatable中,然後遍歷datatable,看這些數據在另一個資料庫的表中是否存在,存在的話就要更新,不存在就要插入。
就這個需求本身來說很簡單,但是隨著數據量的增大,之前通過迴圈遍歷的方式就出現了性能問題。我在思索片刻後,給出的建議是分頁查詢和利用事務批量提交。
1.利用資料庫事務批量提交
1 using (SqlTransaction transaction = targetConnection.BeginTransaction()) 2 { 3 foreach (DataRow row in dataTable.Rows) 4 { 5 if (CheckIfDataExists(targetConnection, transaction, row)) 6 { 7 UpdateData(targetConnection, transaction, row); 8 } 9 else 10 { 11 InsertData(targetConnection, transaction, row); 12 } 13 } 14 15 transaction.Commit(); 16 } 17 } 18 19 //下麵兩個方法都還可以優化,需要接收批量sql語句,所以可以修改成list<SqlCommand>,然後遍歷執行,此處能說明問題即可 20 private void UpdateData(SqlConnection connection, SqlTransaction transaction, DataRow row) 21 { 22 using (SqlCommand command = new SqlCommand("UPDATE YourTable SET YourUpdateStatement WHERE YourCondition", connection, transaction)) 23 { 24 // Add parameters to your command here, based on your update statement and condition 25 // command.Parameters.AddWithValue("@ParameterName", row["ColumnName"]); 26 27 command.ExecuteNonQuery(); 28 } 29 } 30 31 private void InsertData(SqlConnection connection, SqlTransaction transaction, DataRow row) 32 { 33 using (SqlCommand command = new SqlCommand("INSERT INTO YourTable (YourColumns) VALUES (YourValues)", connection, transaction)) 34 { 35 // Add parameters to your command here, based on your columns and values 36 // command.Parameters.AddWithValue("@ParameterName", row["ColumnName"]); 37 38 command.ExecuteNonQuery(); 39 } 40 }
看到這裡的時候,大家可以考慮下,以上方案還有什麼優化的地方嗎?
當然是有的,如果數據量持續增大,datatable這樣直接載入到記憶體的方式恐怕會成為性能問題點吧,我們得考慮怎麼優化才能避免將大數據一次性載入到記憶體,大部分同學第一個想到的就是分頁,這個方案當然是沒有錯,但是還不夠高級,給大家提示一個關鍵字“yield”,或許從聰明的你已經悟到了,接著往下看。
2.流式處理法
什麼是流式處理法呢?
流式處理是一種處理數據的方式,它允許你在數據到達時立即處理,而不是等待所有數據都到達後再處理。這種方式特別適合處理大量數據,因為它不需要一次性載入所有數據到記憶體中。 在C#中,你可以使用yield return關鍵字來創建一個返回IEnumerable<T>的方法,這個方法可以在每次迭代時返回一個元素,而不是一次性返回所有元素。這就是一種流式處理的實現方式。 舉個例子:1 private IEnumerable<DataRow> GetDataFromSource() 2 { 3 using (SqlConnection sourceConnection = new SqlConnection(sourceConnectionString)) 4 { 5 sourceConnection.Open(); 6 7 using (SqlCommand command = new SqlCommand("SELECT * FROM YourTable", sourceConnection)) 8 { 9 using (SqlDataReader reader = command.ExecuteReader()) 10 { 11 DataTable dataTable = new DataTable(); 12 13 while (reader.Read()) 14 { 15 dataTable.LoadDataRow(reader.GetValues(), LoadOption.Upsert); 16 DataRow row = dataTable.Rows[dataTable.Rows.Count - 1]; 17 yield return row; 18 dataTable.Clear(); 19 } 20 } 21 } 22 }在這個示例中,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具體查看哪條鏈路的性能瓶頸 性能優化本身就是一個非常龐大的話題,需要具體問題具體分析,總的來說是,平常能用到的就是以上總結的這些點。技術永遠是為業務服務的,根據不同的業務選擇合適的技術是高級開發者必須要考慮的問題。今天分享就這些了,關於性能優化大家還有那些經驗可以評論區分享! 更多精彩內容,請關註我的V信公眾號:程式員不帥哥