1. 程式人生 > >如何向資料庫叢集快速匯入千萬條資料

如何向資料庫叢集快速匯入千萬條資料

一、資料匯入的方式

向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開發人員提升自己,突破瓶頸,相信你來學習,會有提升和收穫。在這個群裡會有你需要的內容  朋友們請抓緊時間加入進來吧。