最近工作中遇到了一個問題:將mysql的數(shù)據(jù)同步到elasticsearch中,現(xiàn)在有很多方案, logstash-jdbc , elasticsearch-jdbc , go-mysql-elasticsearch,本來原來是使用logstash-jdbc的,但是由于其配置文件是基于ruby語法的,導(dǎo)致遇到問題需要查很多資料,加上logstash調(diào)試困難(很可能是我用的姿勢不對。。。),所以決定手動在elasticsearch中建表,然后寫腳本定期更新數(shù)據(jù),那么問題就來了:第一次插入需要一次性插入以前的所有數(shù)據(jù),以前使用pymysql時用的都是DictCursor游標,原理是一次性講數(shù)據(jù)加載到內(nèi)存中,但是現(xiàn)有表中有幾張有數(shù)千萬行,幾個G大小,一次性讀到內(nèi)存中很不明智,google后,發(fā)現(xiàn)一篇blog不錯,翻譯共享一下(渣英語,不要笑,原文鏈接在文末)。
當(dāng)使用sql查詢的結(jié)果有非常多行時,如果使用默認的cursor,你的程序在接受數(shù)據(jù)的的時候很可能卡住或者被殺死,原因是mysql客戶端(Java,Pyhton)默認在內(nèi)存里緩存下所有行然后再處理,如果內(nèi)存溢出后,你的程序就會被殺死。
解決方式是實用流式游標,在Python中,你可以使用pymysql.cursors.SSCursor(或者SSDictCursor)來解決這個問題
import pymysql
conn = pymysql.connect(...)
cursor = pymysql.cursors.SSCursor(conn)
cursor.execute(...)
while True:
row = cursor.fetchone()
if not row:
break
...
這里有兩點需要注意下:
- 使用pymysql.cursors.SSCursor代替默認的cursor。可以使用以上代碼,或者這樣寫:conn.cursor(pymysql.cursors.SSCursor)
- 使用fetchone去每次只獲得一行,別使用fetchall。也可以使用fetchmay,但是這樣其實是多次調(diào)用fetchone。
對于SSCursor有一個錯誤的理解,就是SSCursor是服務(wù)端一次性讀出所有數(shù)據(jù)然后一條一條返給客戶端,其實不是這樣的,這個cursor實際上沒有緩存下來任何數(shù)據(jù),它不會讀取所有所有到內(nèi)存中,它的做法是從儲存塊中讀取記錄,并且一條一條返回給你。這里有一個更適合的名字:流式游標。
因為SSCursor是沒有緩存的游標,這里有幾條約束:
- 這個connection只能讀完所有行之后才能處理其他sql。如果你需要并行執(zhí)行sql,在另外一個connection中執(zhí)行,否則你會遇到 error 2014 , "Commands out of sync; you can't run this command now."
- 必須一次性讀完所有行,每次讀取后處理數(shù)據(jù)要快,不能超過60s,否則mysql將會斷開這次連接( error2013 , “Lost connection to MySQL server during query),也可以修改 SET NET_WRITE_TIMEOUT = xx 來增加超時間隔。
參考:Techualization: Retrieving million of rows from MySQL(原文更加詳細)