如何向資料庫叢集快速匯入千萬條資料
一、資料匯入的方式
向MySQL資料庫匯入資料,通常有兩種辦法,第一種是利用SOURCE命令,第二種是使用LOAD DATA命令。
SOURCE命令是通過執行SQL檔案中的INSERT語句來實現資料的匯入。正常情況下,如果我們向單節點的MySQL,用INSERT語句批量寫入資料,在普通的PC機上,寫入10萬條資料,大概需要7~8分鐘時間。按照這個速度推算,寫入1千萬條資料大概需要10個小時以上。如果在叢集條件下,這個速度會更慢。
MySQL的叢集分為PXC和Replication叢集。其中Replication叢集是非同步傳輸的,它只保證事物在當前節點成功寫入,資料是否能同步到其他節點,Replication叢集並不能給我們打包票。所以Replication叢集經常出現A節點寫入資料,但是在B節點讀取不到資料的情況。每年Apple秋季釋出會之後,很多人會到Apple官網搶購iPhone手機,然而每年都有顧客會遇到付款之後,訂單依舊是未支付的狀態。這就是典型的Replication叢集的效果,資料出現了不一致。如果採用PXC叢集,因為資料是同步傳輸,所以我們在A節點寫入資料,提交事務的時候,PXC必須保證所有MySQL節點都成功寫入這條資料,才算事務提交成功,所以不會出現A節點寫入資料,在B節點上讀取不到資料的情況。因此PXC更加適合儲存重要的資料,例如交易記錄、學籍資訊、考試成績、使用者資訊等。另外,阿里巴巴在設計OceanBase資料庫的時候也充分借鑑了PXC的原理。
再說回到資料匯入,Replication只保證本節點寫入,而PXC會保證所有節點寫入。因此說,向Replication叢集寫入資料會比PXC快,但都比單節點MySQL速度慢。
SOURCE命令執行的是INSERT語句,所以匯入速度與我們執行INSERT語句沒什麼差別,如果資料量不多,還沒什麼問題,但是呢,如果從遺留資料庫匯出的資料特別多,上千萬,甚至上億。那麼用SOURCE匯入資料就會耗時很長。因此啊,我們要選擇LOAD DATA匯入。
為什麼說LOAD DATA匯入速度比SOURCE快很多倍呢,這是因為資料庫執行SQL語句的時候會先校驗語法,然後優化SQL,最後再執行。但是LOAD DATA匯入的純資料,於是就跳過了SQL的校驗和優化,匯入的速度也就大大提升了。
二、準備工作
下面我們通過程式來生成一個TXT文件,向文件中寫入1千萬條資料,再通過LOAD DATA匯入資料。
這裡我使用IBM的Xtend語言,來生成1千萬條數。Xtend語言可以與Java語言完美相容,除了語法更加簡潔優雅之外,Xtend程式碼會被編譯成Java程式碼。所以我們編寫的程式碼最終會以Java程式來執行。這一點與Kotlin很像,但是Xtend語言的編譯速度明顯是Kotlin的4-6倍,開發效率真的是非常高。從我個人角度來說,非常推薦使用Xtend來改善Java囉嗦的語法。另外,大家可以在Eclipse的軟體商店中找到該外掛,安裝之後,你就能編寫Xtend語言了。
import java.io.FileWriter import java.io.BufferedWriter class Test { def static void main(String[] args) { var writer=new FileWriter("D:/data.txt") var buff=new BufferedWriter(writer) for(i:1..10000000){ buff.write(i+",測試資料 ") } buff.close writer.close } }
接下來我們把TXT檔案上傳到Linux系統,利用split把TXT檔案切分成多個檔案,這樣就可以用Java多執行緒同時把多個TXT檔案匯入到資料庫。
split -l 1000000 -d data.txt
修改MySQL的配置檔案
innodb_flush_log_at_trx_commit = 0
innodb_flush_method = O_DIRECT
innodb_buffer_pool_size = 200M
建立要匯入資料的表
CREATE TABLE t_test(
id INT UNSIGNED PRIMARY KEY,
name VARCHAR(200) NOT NULL
);
三、編寫Java程式,執行多執行緒匯入
因為Java語言自帶了執行緒池,所以我們先定義出來Runnable任務,然後交給多執行緒去執行匯入TXT文件。
import org.eclipse.xtend.lib.annotations.Accessors
import java.io.File
import java.sql.DriverManager
class Task implements Runnable{
@Accessors
File file;
override run() {
var url="jdbc:mysql://192.168.99.131:8066/test"
var username="admin"
var password="Abc_123456"
var con=DriverManager.getConnection(url,username,password)
var sql='''
load data local intfile '/home/data/«file.name»' ignore into table t_test
character set 'utf8'
fields terminated by ',' optionally enclosed by '"'
lines terminated by '
' (id,name);
'''
var pst=con.prepareStatement(sql);
pst.execute
con.close
LoadData.updateNum();
}
}
import com.mysql.jdbc.Driver
import java.sql.DriverManager
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.io.File
class LoadData {
var static int num=0;
var static int end=0;
var static pool=new ThreadPoolExecutor(1,5,60,TimeUnit.SECONDS,new LinkedBlockingQueue(200))
def static void main(String[] args) {
DriverManager.registerDriver(new Driver)
var folder=new File("/home/data")
var files=folder.listFiles
end=files.length //執行緒池結束條件
files.forEach[one|
var task=new Task();
task.file=one;
pool.execute(task)
]
}
synchronized def static updateNum(){
num++;
if(num==end){
pool.shutdown();
println("執行結束")
}
}
}
在Linux系統上執行Java程式。我本地的主機配置是AMD 銳龍2700X,16GB記憶體和固態硬碟,1千萬資料,只用了1分鐘不到的時間就成功匯入了。如果用SOURCE語句匯入這些資料,需要10個小時以上。換做LOAD DATA指令,僅僅1分鐘,速度提升了3萬多倍,太讓人吃驚了。
這是向單節點匯入資料,如果向MySQL叢集匯入資料,該怎麼做呢?首先,如果是Replication叢集,因為節點間是非同步傳輸,所以資料的匯入速度最接近單節點MySQL,因此不用特別優化。如果是PXC叢集,因為節點之間是同步傳輸,所以寫入速度較慢。不妨關閉其他PXC節點,只保留一個MySQL節點,然後向該節點匯入資料。這是為了避免向叢集匯入資料的過程中,同步的速度趕不上寫入的速度,導致PXC叢集限速,從而影響匯入的速度。當我們在一個PXC節點匯入成功之後,再陸續開啟其他PXC節點,那麼就不會產生大規模寫入限速的問題了。
順便給大家推薦一個Java架構方面的交流學習群:698581634,裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系,主要針對Java開發人員提升自己,突破瓶頸,相信你來學習,會有提升和收穫。在這個群裡會有你需要的內容 朋友們請抓緊時間加入進來吧。