最近,有一位同事,諮詢我mysql的一點問題, 具體來講, 是如何很快的將一個mysql導出的文件快速的導入到另一個mysql數據庫。我學習了不少mysql的知識, 使用的時間卻並非不少, 對於mysql導入這類問題,我更是頭一次碰到。詢問個人緣由,我大體能夠猜到,之前互相之間有過不少交流,可能以爲我學習仍是很認真可靠的。html
首先,我瞭解了一下大體的狀況, (1)這個文件是從mysql導出的,文件是運維給的,具體如何生成的,他不知道,能夠詢問運維 (2)按照當前他寫的代碼來看, 每秒能夠插入幾百條數據 (3)按照他們的要求, 須要每秒插入三四千條數據。(4)他們須要插入的數據量達到幾億條數據, 當前有一個上千萬的實驗數據mysql
根據我學習的知識,在《高性能mysql》上面有過描述, load data的速度,比插入數據庫快得多, 因此,我先經過他們從運維那裏獲取了生成數據的代碼,經過向他們尋求了大約幾千條數據。運維生成數據的方式是:select ... into outfile, 根據mysql官方文檔上的說明,正好能夠經過load data加載回數據庫,load data使用的fields和lines等參數, 能夠經過運維給出的語句獲得,測試一次,成功。在個人推薦下,咱們先使用innodb引擎根據測試的結果顯示,大約每秒1千多。這個,我是經過date; mysql -e "load data ..."; date; 來大體獲得。考慮到innodb中存儲帶索引的數據,插入速度會隨着數據量的增長而變慢,那麼咱們使用幾千條數據測試的結果,應該超過1千多。咱們又在目標電腦上進行測試,獲得的結果基本一致,速度會略快,具體緣由當時沒有細查。向遠程mysql導入數據,須要進行必定的配置,具體配置的說明,在這裏省略。由於目標電腦有額外的用處,因此,咱們決定如今本機電腦上進行測試。sql
進行了初步的準備工做以後,我決定獲取更多的數據,此次我打算下拉10萬條數據。但是個人那位同事,經過對csv文件進行剪切,獲得的結果,不能在個人mysql上進行很好的加載(load data)。因而,咱們打算從有上千萬條數據的測試mysql服務器上進行拉取。但是SELECT ... INTO OUTFILE只會將文件下拉到本地,咱們沒有本地的權限,除非找運維。爲此,咱們換了一種思路,使用select語句,模擬select ... into outfile語句,具體作法以下:
shell
SET autocommit=0; ... SQL import statements ... COMMIT;
(2)臨時取消unique索引的檢查:數據庫
SET unique_checks=0; ... SQL import statements ... SET unique_checks=1;
這裏簡單介紹一下,個人測試使用表格和數據,個人create table以下:bash
CREATE TABLE `tick` ( `id` int(11) NOT NULL, `val` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
行數爲1775232,插入時間大約在十幾秒。在測試過程當中,由於設計頻繁的清空表格,清空表格須要花好幾秒鐘的時間,建議作相似操做的能夠考慮,先drop table,而後再create table,這樣速度會明顯更快。按照在個人電腦上的測試結果,二者基本沒有什麼區別,大約都在18秒左右。使用的mysql語句爲:服務器
date; mysql -u root -pmysql -e "load data infile './tick.txt' into table test.tick;";date; mysql -u root -pmysql -e "set autocommit=0;set unique_checks=0;load data infile '/home/sun/tick.txt' into table test.tick;set unique_checks=1;set autocommit=1;";date;
爲了確保個人設置生效,我檢查了status輸出:session
mysql -u root -pmysql -e "set autocommit=0;set unique_checks=0; show variables like '%commit';set unique_checks=1;set autocommit=1;";date; date; mysql -u root -pmysql -e "set autocommit=0;set unique_checks=0; show variables like 'unique_checks';set unique_checks=1;set autocommit=1;";date;
輸出結果以下:多線程
autocommit爲OFF, unique_checks爲OFF。運維
其實我還設置過不少配置,例如插入緩衝區大小一類的,其中比較有效的是innodb_flush_log_at_trx_commit設置爲2,對於咱們這個問題,這個設置是能夠考慮的。
我一直有一個疑問,就是說,load data是否爲多線程運行的,按照我以往使用mysql的經驗,和《高性能mysql》對load data的論斷(遠比insert快),若是多線程加載,不至於速度如此之慢。後來,我嘗試使用mysqlimport,通過查看mysqlimport上面指定的--use-threads爲多線程讀取文件,以及在使用mysqlimport加載時,CPU利用率最多隻有百分之兩百多一點,這個讓我以爲頗有多是單線程執行的。經過在運行load data的同時,調用show full processlist,能夠清楚的看到,load data是單線程運行的。結果以下:
*************************** 1. row ***************************
Id: 4
User: event_scheduler
Host: localhost
db: NULL
Command: Daemon
Time: 2835
State: Waiting on empty queue
Info: NULL
*************************** 2. row ***************************
Id: 17
User: root
Host: localhost
db: test
Command: Query
Time: 0
State: starting
Info: show full processlist
*************************** 3. row ***************************
Id: 39
User: root
Host: localhost
db: NULL
Command: Query
Time: 13
State: executing
Info: load data infile '/home/sun/tick.txt' into table test.tick
3 rows in set (0.00 sec)
由上能夠看出,load data是單線程執行的。
我知道,使用多線程執行,會是一個很好的辦法,只是,我仍是有想法提升單線程的效率,我想到了set profiling. 經過在一個session中設置set profiling = 1; 能夠查看一個語句詳細的時間消耗。我使用了以下的mysql語句:
set profiling=1; load data infile '/home/sun/tick.txt' into table test.tick; show profiles; show profile for query 1;
能夠獲得以下的結果:
+----------------------+-----------+
| Status | Duration |
+----------------------+-----------+
| starting | 0.000102 |
| checking permissions | 0.000016 |
| Opening tables | 0.001377 |
| System lock | 0.000024 |
| executing | 17.476604 |
| query end | 0.233398 |
| closing tables | 0.000045 |
| freeing items | 0.000039 |
| cleaning up | 0.000047 |
+----------------------+-----------+
可見執行時間佔據了最長的時間,而執行時間應該主要是CPU密集型的(這個我沒有足夠的把握),從CPU的使用來看,這個論斷應該是比較合理的。對於一些比較複雜的問題,我不建議這樣分析,固然,這裏也能夠考慮不這樣分析,能夠借鑑《高性能mysql》中的作法,參考以下的腳本:
#!/bin/sh INTERVAL=5 PREFIX=$INTERVAL-sec-status RUNFILE=/home/sun/benchmarks/running mysql -e 'SHOW GLOBAL VARIABLES' >> mysql-variables while test -e $RUNFILE; do file=$(date +%F_%I) sleep=$(date +%s.%N | awk "{print $INTERVAL - (\$1 % $INTERVAL)}") sleep $sleep ts="$(date +"TS %s.%N %F %T")" loadavg="$(uptime)" echo "$ts $loadavg" >> $PREFIX-${file}-status mysql -e 'SHOW GLOBAL STATUS' >> $PREFIX-${file}-status & echo "$ts $loadavg" >> $PREFIX-${file}-innodbstatus mysql -e 'SHOW ENGINE INNODB STATUS\G' >> $PREFIX-${file}-innodbstatus & echo "$ts $loadavg" >> $PREFIX-${file}-processlist mysql -e 'SHOW FULL PROCESSLIST\G' >> $PREFIX-${file}-processlist & echo $ts done echo Exiting because $RUNFILE does not exist
調整中間的時間,打印出詳細的信息,進行分析。我這裏就沒有作這件事。
下面考慮了多線程處理,查看select ... into outfile的文件,即tick.txt可知,文件中的每行對應於表中的一條數據,想把文件切分,可使用wc -l得出行數,而後使用head與tail獲得兩個文件,而後對這兩個文件再次使用head和tail,獲得測試用的四個文件。進行這個測試,我寫了很簡單的兩個shell腳本:
#!/bin/bash date;mysql -u root -pmysql -e "load data infile '/home/sun/$1' into table test.tick;";date; exit 0; #!/bin/bash ./mysql-load.sh data1 & ./mysql-load.sh data2 & ./mysql-load.sh data3 & ./mysql-load.sh data4 & exit 0;
經過測試,能夠發現加載這些文件所用的時間由18秒下降到10秒。我還測試了加載兩個文件,所用時間由18秒下降到12.5秒,可見多線程加載能夠明顯提升加載速度。由於個人電腦是4線程的,因此我決定使用真正的測試數據進行測試。
按照咱們之間出現的一個小插曲,發現使用MYISAM的插入速度會明顯快於InnoDB,使用MYISAM插入速度能夠達到每秒八千左右。個人同事認爲這個能夠知足他的需求,因此就採用了MYISAM。那時,個人同事發現他的插入速度明顯我以前測試的要快,但願我能找出緣由,基於沒有mysql沒有進行什麼特殊的配置,他的電腦配置和個人電腦配置基本一致,我以爲應該是表格的問題,可能性最大的就是引擎,後來發現他無心間使用了MYISAM做爲存儲引擎。關於兩個電腦配置的差異,能夠經過show variables將配置變量導入到文件中進行對比獲得,這個屬於後話。既然個人同事以爲那樣可行,我就沒有進一步測試多線程的效果,只是這個經歷能夠記錄下來,留之後借鑑。