簡述mysql問題處理

最近,有一位同事,諮詢我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

(1) 將數據select到本地,這裏須要注意一件事,order by後面應該跟一個索引,這樣能夠避免排序操做。我進行過測試,若是不顯示制定order by index, 下拉所用的時間會明顯增長。
mysql -h 192.168.9.10 -u root -p -e "SELECT * FROM edu.olog order by idd LIMIT 100000;" > /home/sun/data.txt
(2) 使用sed命令移除輸出中的第一行
sed -i '1d' /home/sun/data.txt
(3) 對遠程數據庫表格調用show create table指令獲得表格的建立指令。在本機mysql的一個數據庫中調用上述的建立表格的指令,這裏可能須要作修改引擎一類的操做, 例如使用MYISAM做爲表格的引擎,這樣對於咱們的加載任務來講,load data的速度明顯更快。而後,對生成的文件調用load data記載到本機數據庫, 不過沒有FIELDS, lines一類的後綴。
這樣,咱們就能夠對本機表格調用select ... into outfile生成對應的csv文件。
咱們對十萬條數據進行了測試,測試的結果顯示,加載速度大約每秒1千多,速度比以前幾千條時略慢。
 
咱們已經知道了目標,也知道了當前的狀態,那麼下一步就是優化。首先,我查找了一些資料,這裏以mysql8.0爲例。首先查看了load data的文檔,具體網頁爲:https://dev.mysql.com/doc/refman/8.0/en/load-data.html。考慮到load data相似於insert,我又在優化的相關模塊中進行查找,大的目錄爲:https://dev.mysql.com/doc/refman/8.0/en/optimization.html(優化),緊接着查找的網頁是:https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html(插入優化), 根據這個網頁的提示,最後查看的網頁是:https://dev.mysql.com/doc/refman/8.0/en/optimizing-innodb-bulk-data-loading.html(優化innodb表格的加載大量數據)。根據這裏面的提示,我對我本身構建的測試數據進行了測試,使用的優化方法以下:
(1)臨時修改自動提交的方式:
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將配置變量導入到文件中進行對比獲得,這個屬於後話。既然個人同事以爲那樣可行,我就沒有進一步測試多線程的效果,只是這個經歷能夠記錄下來,留之後借鑑。

相關文章
相關標籤/搜索