Python多執行緒批量插入資料小結
在測試的過程中,無法避免的需要做一些效能壓測,造資料的時長在此時就會備受關注。比如,造資料的時候用多執行緒還是多程序,用直接插入DB方式還是用先寫檔案後匯入mysql的方式,寫檔案是寫批量sql後面source匯入,還是寫文字格式後面load ito file匯入,使用不同的方法耗時結果肯定是不一樣的。除此之外,還有mysql的版本,DB的引擎,表的結構設計這些都會影響大量資料插入的時間。
這次匯入資料做了一個小試驗:匯入2000w筆資料到DB內。使用多執行緒的執行緒池技術,首先寫2000w筆資料分成M個檔案,然後使用N個執行緒去併發處理寫好M個檔案,並把M個檔案匯入到MYSQL中。之前同事寫的檔案後面都是用load data from file命令匯入的,官網也有說明這個命令字跟insert比,效能會高出20倍,於是把這個命令字跟我常用的source命令字做了下對比,結論如下:
一、Insert命令字詳解
基於這些試驗和現象,確定load ito file載入資料的效能確實要比source匯入大批量資料的效能更穩定,更快。但這又是為什麼呢?查閱了下MYSQL的官網,insert命令字的插入過程如下,括號內的數字幾乎表示可能佔用的耗時比:
A: 開啟連線:(3)
B: 向伺服器傳送查詢:(2)
C:解析查詢:(2)
D:插入行:(1×行大小)
E:插入索引:(1×數索引)
F:關閉連線(1)
以上步驟還不包含連線時,開啟表的開銷。 當大量的insert批量檔案被多執行緒執行插入時,每一個執行緒都需要經過6步才能完成資料的插入,表的索引結構,表當前資料的行數對insert的每次插入都會影響。如果想提升寫入大資料的效能,可以嘗試批量insert(即insert後的值有多個values),這在一般情況下會單個insert要快,但是要注意設定mysql的bulk_insert_buffer_size引數的大小,之前開發有一些經驗值,一般情況下是設定300-500一批插入效能最佳。但是想要效能更快更穩定,可以使用“LOAD DATA INFILE”,這個命令比單insert要快近20倍。
二、提升匯入資料效能----mysql伺服器端
1、當將資料匯入到INNODB中時,關閉自動提交模式,因為在自動提交模式下,每一次插入都會重新整理一次日誌到磁碟。可以使用如下語句:
SET autocommit=0;
... SQL import statements ...
COMMIT;
2、關閉唯一索引。減少索引的插入和唯一性的校驗。
SET unique_checks=0;
... SQL import statements ...
SET unique_checks=1;
3、關閉外來鍵檢查來加速表匯入。
SET foreign_key_checks=0;
... SQL import statements ...
SET foreign_key_checks=1;
4、將innodb_autoinc_lock_mode設定為2,而不是預設值1。
5、在執行批量插入時,以主鍵順序插入行更快。
6、字串的拼接用 .join > a += b ,因為 +=方式每次要重新計算記憶體/分配。
7、Python的多執行緒在IO密集的應用場景下,可以寫多個檔案,讓多執行緒的優勢得到更充分的發揮。
8、批量插入時,多利用欄位的預設值,欄位值如果使用預設值,會縮端插入過程中對資料解析的時間。
三、過程遇到的問題以及解決辦法
1、怎麼快速刪除2000w筆資料?
使用truncate 命令字,幾秒內就能刪除資料。如 :truncate table t_dc;
2、測試機器上網速很慢,無法安裝python的第三方庫怎麼辦?
在已經安裝好的python的site-packages下複製使用的py指令碼,然後放到對應機器上。如threadpool.py無法安裝時,最快速的方法就是手工複製檔案Python27\Lib\site-packages\threadpool.py使用。
3、mysqldb執行報錯:Lock wait timeout exceeded; try restarting transaction
設定全域性等待事務鎖超時時間 :SET GLOBAL innodb_lock_wait_timeout=100;
查詢全域性等待事務鎖超時時間 :SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout';
4、mysql事務鎖如何檢視:
在information_schema下面有三張表:INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS(解決問題方法),通過這三張表,可以更簡單地監控當前的事務並分析可能存在的問題。
當前執行的所有事務 :select * from information_schema.innodb_trx;
當前出現的鎖 :select * from information_schema.innodb_locks;
鎖等待的對應關係:select * from information_schema.innodb_lock_waits;
四:批量指令碼(執行緒池+lLOAD DATA LOCAL INFILE)
#!/usr/bin/env python
#coding=GBK
import threadpool
import time,sys
from subprocess import call,Popen,PIPE
COUNT = 1000
process_num = 5
seperate = 100
dbHost='100.92.174.16'
dbUser='root'
dbPasswd='root1234'
dbOperater=None
baseData = {}
thread_arr=[]
detail_data={}
listid = 16080802011100100001
Ftde_id = 1
Fbank_list = 20171217761623447701
tablename = "epcc_check.t_dc_list"
resultDir = "/data/home/loleinaliao/loleinatext/"
#columns
order_columns="Fcheck_bank, Facc_day, Fbankaccno, Fbankusername, Famount, Fdc_type_id, Fori_accno, Fbank_status,Fid,Fbank_listid,Fbatchno"
#data
order_base="'4251','2018022721','6225425177777777800004','wltest','1','16','6225425177777777800004','00'"
tablename ="epcc_check_201810.t_dc_list_06"
def writeDownSqlData(fileName,content):
fo = open(fileName,"w")
fo.write(content)
fo.close()
def make_t_tcpay_list(deal_num,Flistid,Ftde_id,Fbank_list):
filename ="data_order_"+Fbank_list+".text"
Fbatchno = 'B201810070011'
orderDataList = []
for i in range(int(deal_num)):
orderData=""
orderData = "%s,'%s','%s','%s'" % (order_base, Ftde_id, Fbank_list, Fbatchno)
orderDataList.append(orderData)
Ftde_id = int(Ftde_id) + 1
Flistid = int(Flistid) + 1
Fbank_list = int(Fbank_list) + 1
writeDownSqlData(resultDir+filename, "\n".join(orderDataList) + "\n")
loadDataIntoDB(resultDir+filename,tablename,order_columns)
def loadDataIntoDB(filename,tableName,order_columns):
mysqlCmd = r"LOAD DATA LOCAL INFILE '%s' into table %s FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\'' LINES TERMINATED BY '\n' (%s"%(filename,tableName,order_columns)+")"
mysqlConCmd = r"mysql -u%s -p%s -h%s"%(dbUser,dbPasswd,dbHost)
runCmd = mysqlConCmd+' -e "'+mysqlCmd+'"'
result = call(runCmd,shell=True)
if result != 0:
print "load local data into database failed,exit "
sys.exit()
if __name__ == "__main__":
start = time.time()
begin_Flistid ='110180809100012153304210311120'
begin_Ftde_id ="1"
begin_Fbank_seq ="2018100800000110734321790770100"
total_num =20000000
threadpool_num = 20
func_var=[]
seperate =2000
pool = threadpool.ThreadPool(threadpool_num)
for i in range(seperate):
list_temp =[]
list_temp =[str(total_num/seperate),begin_Flistid,begin_Ftde_id,begin_Fbank_seq]
func_var.append((list_temp,None))
begin_Flistid = str(int(begin_Flistid)+ total_num/threadpool_num)
begin_Ftde_id = str(int(begin_Ftde_id)+ total_num/threadpool_num)
begin_Fbank_seq = str(int(begin_Fbank_seq)+ total_num/threadpool_num)
pool = threadpool.ThreadPool(threadpool_num)
requests = threadpool.makeRequests(make_t_tcpay_list, func_var)
for req in requests:
pool.putRequest(req)
pool.wait()
end = time.time()
print end - start
Linux公社的RSS地址 : ofollow,noindex" target="_blank">https://www.linuxidc.com/rssFeed.aspx
本文永久更新連結地址: https://www.linuxidc.com/Linux/2018-11/155255.htm