血通常的教訓,請慎用 insert into select。同事應用以後,致使公司損失了近 10w 元,最終被公司開除。面試
公司的交易量比較大,使用的數據庫是 MySQL,天天的增量差很少在百萬左右,公司並無分庫分表,因此想維持這個表的性能只能考慮作數據遷移。算法
同事李某接到了這個任務,因而他想出了這兩個方案:數據庫
先經過程序查詢出來,而後插入歷史表,再刪除原表。網絡
使用 insert into select 讓數據庫 IO 來完成全部操做。post
第一個方案使用的時候發現一次性所有加載,系統直接就 OOM 了,可是分批次作就過多 IO 和時間長,因而選用了第二種方案,測試的時候沒有任何問題,開開心心上線,而後被開除。性能
到底發生了啥?咱們覆盤一下。學習
先來看第一個方案,先看僞代碼:測試
// 一、查詢對應須要遷移的數據 3d
List<Object> list = selectData(); 視頻
// 二、將數據插入歷史表
insertData(list);
// 三、刪除原表數據
deleteByIds(ids);
咱們能夠從這段代碼中看到,OOM 的緣由很簡單,咱們直接將數據所有加載內存,內存不爆纔怪。
再來看看第二個方案,到底發生了啥?
爲了維持表的性能,同時保留有效數據,通過商量定了一個量,保留 10 天的數據,差很少要在表裏面保留 1kw 的數據。
因此同事就作了一個時間篩選的操做,直接 insert into select ... dateTime < (Ten days ago)...
爽極了,直接就避免了要去分頁查詢數據,這樣就不存在 OOM 啦。還簡化了不少的代碼操做,減小了網絡問題。
爲了測試,還特地建了 1kw 的數據來模擬,測試環境固然是沒有問題啦,順利經過。
考慮到這個表是一個支付流水錶,因而將這個任務作成定時任務,而且定在晚上 8 點執行。
晚上量也不是很大,天然是沒有什麼問題,可是次日公司財務上班,開始對帳,發現資金對不上,不少流水都沒有入庫。
最終排查發現晚上 8 點以後,陸陸續續開始出現支付流水插入失敗的問題,不少數據所以丟失。
最終定位到了是遷移任務引發的問題,剛開始還不明因此,白天沒有問題,而後想到晚上出現這樣的狀況多是晚上的任務出現了影響,最後停掉該任務的第二次上線,發現沒有了這樣的狀況。
問題在哪裏?爲何停掉遷移的任務以後就行了呢?這個 insert into select 操做到底作了什麼?
咱們來看看這個語句的 explain:
咱們不難從圖中看出,這個查詢語句直接走了全表掃描。這個時候,咱們不難猜測到一點點問題。
若是全表掃描,咱們這個表這麼大,是否是意味着遷移的時間會很長?倘若咱們這個遷移時間爲一個小時,那是否是意味着就解釋了咱們白天沒有出現這樣問題的緣由了。可是全表掃描是最根本的緣由嗎?
咱們不妨試試,一邊遷移,一邊作些的操做,還原現場。最終仍是會出現這樣的問題。
這個時候,咱們能夠調整一下,大膽假設,若是不全表掃描,是否是就不會出現這樣的問題。當咱們將條件修改以後,果真發現沒有走了全表掃描了。
最終再次還原現場,問題解決了:
得出結論:全表掃描致使了此次事故的發生。這樣作就解決了發生的問題,可是作爲陸陸續續開始失敗這個就很差解釋了。
在默認的事務隔離級別下:insert into a select b 的操做 a 表示直接鎖表,b 表是逐條加鎖。這也就解釋了爲何出現陸續的失敗的緣由。
在逐條加鎖的時候,流水錶因爲多數是複合記錄,因此最終部分在掃描的時候被鎖定,部分拿不到鎖,最終致使超時或者直接失敗,還有一些在這加鎖的過程當中成功了。
在測試的時候充分的使用了正式環境的數據來測試,可是別忽視一個問題,那就是測試環境畢竟是測試環境,在測試的時候,數據量真實並不表明就是真實的業務場景。
比方說,這個狀況裏面就少了一個遷移的時候,大量數據的插入這樣的狀況。最終致使線上 Bug。
既然咱們避免全表掃描就能夠解決,咱們避免它就好了。想要避免全表掃描,對 where 後面的條件作索引,讓咱們的 select 查詢都走索引便可。
insert into 還能用嗎?回答是:固然能夠。
使用 insert into select 的時候請慎重,必定要作好索引。
最後,特別推薦一個分享C/C++和算法的優質內容,學習交流,技術探討,面試指導,簡歷修改...還有超多源碼素材等學習資料,零基礎的視頻等着你!
還沒關注的小夥伴,能夠長按關注一下:
做者:xlecho
編輯:陶家龍
出處:https://juejin.cn/post/6931890118538199048