C#如何在海量數(shù)據(jù)下的高效讀取寫入MySQL
前提
由于工作的原因,經常需要對海量數(shù)據(jù)進行處理,做的數(shù)據(jù)爬蟲相關,動輒千萬級別的數(shù)據(jù),單表幾十個G都是都是家常便飯。 主要開發(fā)語言是C#,數(shù)據(jù)庫使用的是MySQL。
最常見的操作便是 select 讀取數(shù)據(jù),然后在C#中對數(shù)據(jù)進行處理, 完畢后再插入數(shù)據(jù)庫中。 簡而言之就 select -> process -> insert三個步驟。 對于數(shù)據(jù)量小的情況下(百萬級別 or 幾百兆)可能最多1個小時就處理完了。但是對于千萬級數(shù)據(jù)可能幾天,甚至更多。 那么問題來了,如何優(yōu)化??
(數(shù)據(jù)庫的一覽,有圖有真相)
第一步 解決讀取的問題
跟數(shù)據(jù)庫打交道的方式有很多,我來列舉下吧:
1. 【重武器-坦克大炮】使用重型ORM框架,比如EF,NHibernat 這樣的框架。
2. 【輕武器-AK47】 使用Dapper,PetaPoco之類,單cs文件。靈活高效,使用簡單。居家越貨必備(我更喜歡PetaPoco :))
3. 【冷兵器?匕首?】使用原生的Connection、Command。 然后寫原生的SQL語句。。
分析:
【重武器】在我們這里肯定直接被PASS, 他們應該被用在大型項目中。
【輕武器】Dapper,PetaPoco 看過源碼你會發(fā)現(xiàn)用到了反射,雖然使用IL和緩存技術,但是還是會影響讀取效率,PASS
好吧那就只有使用匕首,原生SQL走起, 利用DataReader 進行高效讀取,并且使用索引取數(shù)據(jù)(更快),而不是列名。
大概的代碼如下:
using (var conn = new MySqlConnection('Connection String...')) { conn.Open(); //此處設置讀取的超時,不然在海量數(shù)據(jù)時很容易超時 var c = new MySqlCommand('set net_write_timeout=9999999; set net_read_timeout=9999999', conn); c.ExecuteNonQuery(); MySqlCommand rcmd = new MySqlCommand(); rcmd.Connection = conn; rcmd.CommandText = @'SELECT `f1`,`f2` FROM `table1`'; //設置命令的執(zhí)行超時 rcmd.CommandTimeout = 99999999; var myData = rcmd.ExecuteReader(); while (myData.Read()) { var f1= myData.GetInt32(0); var f2= myData.GetString(1); //這里做數(shù)據(jù)處理.... } }
哈哈,怎么樣,代碼非常原始,還是使用索引來取數(shù)據(jù),很容易出錯。 當然一切為了性能咱都忍了
第二步 數(shù)據(jù)處理
其實這一步,根據(jù)你的業(yè)務需要,代碼肯定不一, 不過無非是一些字符串處理,類型轉換的操作,這時候就是考驗你的C#基礎功底的時候了。 以及如何高效編寫正則表達式。。。
具體代碼也沒法寫啊 ,先看完CLR via C# 在來跟我討論吧 ,O(∩_∩)O哈哈哈~ 跳過。。。。
第三部 數(shù)據(jù)插入
如何批量插入才最高效呢? 有同學會說, 使用事務啊,BeginTransaction, 然后EndTransaction。 恩,這個的確可以提高插入效率。 但是還有更加高效的方法,那就是合并insert語句。
那么怎么合并呢?
insert into table (f1,f2) values(1,'sss'),values(2,'bbbb'),values(3,'cccc');
就是把values后面的全部用逗號,鏈接起來,然后一次性執(zhí)行 。
當然不能一次性提交個100MB的SQL執(zhí)行,MySQL服務器對每次執(zhí)行命令的長度是有限制的。 通過 MySQL服務器端的max_allowed_packet 屬性可以查看, 默認是1MB
咱們來看看偽代碼吧
//使用StringBuilder高效拼接字符串 var sqlBuilder = new StringBuilder(); //添加insert 語句的頭 string sqlHeader = 'insert into table1 (`f1`,`f2`) values'; sqlBuilder.Append(sqlHeader); using (var conn = new MySqlConnection('Connection String...')) { conn.Open(); //此處設置讀取的超時,不然在海量數(shù)據(jù)時很容易超時 var c = new MySqlCommand('set net_write_timeout=9999999; set net_read_timeout=9999999', conn); c.ExecuteNonQuery(); MySqlCommand rcmd = new MySqlCommand(); rcmd.Connection = conn; rcmd.CommandText = @'SELECT `f1`,`f2` FROM `table1`'; //設置命令的執(zhí)行超時 rcmd.CommandTimeout = 99999999; var myData = rcmd.ExecuteReader(); while (myData.Read()) { var f1 = myData.GetInt32(0); var f2 = myData.GetString(1); //這里做數(shù)據(jù)處理.... sqlBuilder.AppendFormat('({0},'{1}'),', f1,AddSlash(f2)); if (sqlBuilder.Length >= 1024 * 1024)//當然這里的1MB length的字符串并不等于 1MB的Packet。。。我知道:) { insertCmd.Execute(sqlBuilder.Remove(sqlBuilder.Length-1,1).ToString())//移除逗號,然后執(zhí)行 sqlBuilder.Clear();//清空 sqlBuilder.Append(sqlHeader);//在加上insert 頭 } } }
好了,到這里 大概的優(yōu)化后的高效查詢、插入就完成了。
結語
總結下來,無非2個關鍵技術點,DataReader、SQL合并,都是一些老的技術啦。
其實,上面的代碼只能稱得上 高效 , 但是, 卻非常的不優(yōu)雅。。。甚至難看。。。
那那么問題來了? 如何進行重構呢? 通過重構抽象出一個可用的類,而不必關心字符串拼接這些亂七八糟的東西,支持多線程合并寫入,最大限度提高寫入IO, 我們在下一篇文章中再來談談。
- C#如何連接MySQL數(shù)據(jù)庫
- C#連接MySQL操作詳細教程
- C#實現(xiàn)MySQL命令行備份和恢復
- C#連接MySQL的兩個簡單代碼示例
- C#連接mysql的方法【基于vs2010】
- C#在MySQL大量數(shù)據(jù)下的高效讀取、寫入詳解
- c#幾種數(shù)據(jù)庫的大數(shù)據(jù)批量插入(SqlServer、Oracle、SQLite和MySql)
- C#中調用MySQL存儲過程的方法
- C#連接mysql數(shù)據(jù)庫完整實例
- C#實現(xiàn)操作MySql數(shù)據(jù)層類MysqlHelper實例
- c# 向MySQL添加數(shù)據(jù)的兩種方法
相關文章
mysql error:#1062 Duplicate entry ‘***′ for key 1問題解決方法
今天公司的一個網站突然提示MySQL Error Duplicate entry '96624' for key 1錯誤,經過分析這個問題是由于mysql表中的一個id自增長字段導致。2011-09-09MySQL索引下推index condition pushdown
索引下推是MySQL 5.6版本引入的一種數(shù)據(jù)庫查詢優(yōu)化技術,本文主要介紹了MySQL索引下推index condition pushdown,具有一定的參考價值,感興趣的可以了解一下2024-08-08Django2.* + Mysql5.7開發(fā)環(huán)境整合教程圖解
這篇文章主要介紹了Django2.* + Mysql5.7開發(fā)環(huán)境整合教程,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09mysql 記錄不存在時插入 記錄存在則更新的實現(xiàn)方法
相信很多人都需要用到這個語句,請看下文:(在4.1以后的版本才有效)2008-08-08Workbench通過遠程訪問mysql數(shù)據(jù)庫的方法詳解
這篇文章主要給大家介紹了Workbench通過遠程訪問mysql數(shù)據(jù)庫的相關資料,文中通過圖文介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-06-06MySQL創(chuàng)建新用戶、增加賬戶的2種方法及使用實例
這篇文章主要介紹了MySQL創(chuàng)建新用戶、增加賬戶的2種方法及使用實例,本文同時也介紹了更新和修改密碼的方法,需要的朋友可以參考下2014-12-12mysql:Can''t start server: can''t create PID file: No space
這篇文章主要介紹了mysql啟動失敗不能正常啟動并報錯Can't start server: can't create PID file: No space left on device問題解決方法,需要的朋友可以參考下2015-05-05