MySQL數據查詢太多會OOM嗎?

      網友投稿 605 2022-05-28

      我的主機內存只有100G,現在要全表掃描一個200G大表,會不會把DB主機的內存用光?

      邏輯備份時,可不就是做整庫掃描嗎?若這樣就會把內存吃光,邏輯備份不是早就掛了?

      所以大表全表掃描,看起來應該沒問題。這是為啥呢?

      全表掃描對server層的影響

      假設,我們現在要對一個200G的InnoDB表db1. t,執行一個全表掃描。當然,你要把掃描結果保存在客戶端,會使用類似這樣的命令:

      mysql -h$host -P$port -u$user -p$pwd -e "select * from db1.t" > $target_file

      1

      2

      InnoDB數據保存在主鍵索引上,所以全表掃描實際上是直接掃描表t的主鍵索引。這條查詢語句由于沒有其他判斷條件,所以查到的每一行都可以直接放到結果集,然后返回給客戶端。

      那么,這個“結果集”存在哪里呢?

      服務端無需保存一個完整結果集。取數據和發數據的流程是這樣的:

      獲取一行,寫到net_buffer。這塊內存的大小是由參數net_buffer_length定義,默認16k

      重復獲取行,直到net_buffer寫滿,調用網絡接口發出去

      若發送成功,就清空net_buffer,然后繼續取下一行,并寫入net_buffer

      若發送函數返回EAGAIN或WSAEWOULDBLOCK,就表示本地網絡棧(socket send buffer)寫滿了,進入等待。直到網絡棧重新可寫,再繼續發送

      查詢結果發送流程

      可見:

      一個查詢在發送過程中,占用的Mysql內部的內存最大就是net_buffer_length這么大,不會達到200G

      socket send buffer 也不可能達到200G(默認定義/proc/sys/net/core/wmem_default),若socket send buffer被寫滿,就會暫停讀數據的流程

      所以MySQL其實是“邊讀邊發”。這意味著,若客戶端接收得慢,會導致MySQL服務端由于結果發不出去,這個事務的執行時間變長。

      比如下面這個狀態,就是當客戶端不讀socket receive buffer內容時,在服務端show processlist看到的結果。

      服務端發送阻塞

      若看到State一直是“Sending to client”,說明服務器端的網絡棧寫滿了。

      若客戶端使用–quick參數,會使用mysql_use_result方法:讀一行處理一行。假設某業務的邏輯較復雜,每讀一行數據以后要處理的邏輯若很慢,就會導致客戶端要過很久才取下一行數據,可能就會出現上圖結果。

      因此,對于正常的線上業務來說,若一個查詢的返回結果不多,推薦使用mysql_store_result接口,直接把查詢結果保存到本地內存。

      當然前提是查詢返回結果不多。如果太多,因為執行了一個大查詢導致客戶端占用內存近20G,這種情況下就需要改用mysql_use_result接口。

      若你在自己負責維護的MySQL里看到很多個線程都處于“Sending to client”,表明你要讓業務開發同學優化查詢結果,并評估這么多的返回結果是否合理。

      若要快速減少處于這個狀態的線程的話,可以將net_buffer_length設置更大。

      有時,實例上看到很多查詢語句狀態是“Sending data”,但查看網絡也沒什么問題,為什么Sending data要這么久?

      一個查詢語句的狀態變化是這樣的:

      MySQL查詢語句進入執行階段后,先把狀態設置成 Sending data

      然后,發送執行結果的列相關的信息(meta data) 給客戶端

      再繼續執行語句的流程

      執行完成后,把狀態設置成空字符串。

      即“Sending data”并不一定是指“正在發送數據”,而可能是處于執行器過程中的任意階段。比如,你可以構造一個鎖等待場景,就能看到Sending data狀態。

      讀全表被鎖:

      Sending data狀態

      可見session2是在等鎖,狀態顯示為Sending data。

      僅當一個線程處于“等待客戶端接收結果”的狀態,才會顯示"Sending to client"

      若顯示成“Sending data”,它的意思只是“正在執行”

      所以,查詢的結果是分段發給客戶端,因此掃描全表,查詢返回大量數據,并不會把內存打爆。

      以上是server層的處理邏輯,在InnoDB引擎里又是怎么處理?

      全表掃描對InnoDB的影響

      InnoDB內存的一個作用,是保存更新的結果,再配合redo log,避免隨機寫盤。

      內存的數據頁是在Buffer Pool (簡稱為BP)管理,在WAL里BP起加速更新的作用。

      BP還能加速查詢。

      由于WAL,當事務提交時,磁盤上的數據頁是舊的,若這時馬上有個查詢來讀該數據頁,是不是要馬上把redo log應用到數據頁?

      不需要。因為此時,內存數據頁的結果是最新的,直接讀內存頁即可。這時查詢無需讀磁盤,直接從內存取結果,速度很快。所以,Buffer Pool能加速查詢。

      而BP對查詢的加速效果,依賴于一個重要的指標,即:內存命中率。

      可以在show engine innodb status結果中,查看一個系統當前的BP命中率。一般情況下,一個穩定服務的線上系統,要保證響應時間符合要求的話,內存命中率要在99%以上。

      執行show engine innodb status ,可以看到“Buffer pool hit rate”字樣,顯示的就是當前的命中率。比如下圖命中率,就是100%。

      若所有查詢需要的數據頁都能夠直接從內存得到,那是最好的,對應命中率100%。

      InnoDB Buffer Pool的大小是由參數 innodb_buffer_pool_size確定,一般建議設置成可用物理內存的60%~80%。

      在大約十年前,單機的數據量是上百個G,而物理內存是幾個G;現在雖然很多服務器都能有128G甚至更高的內存,但是單機的數據量卻達到了T級別。

      所以,innodb_buffer_pool_size小于磁盤數據量很常見。若一個 Buffer Pool滿了,而又要從磁盤讀入一個數據頁,那肯定是要淘汰一個舊數據頁的。

      InnoDB內存管理

      使用的最近最少使用 (Least Recently Used, LRU)算法,淘汰最久未使用數據。

      基本LRU算法

      TODO

      InnoDB管理BP的LRU算法,是用鏈表實現的:

      state1,鏈表頭部是P1,表示P1是最近剛被訪問過的數據頁

      此時,一個讀請求訪問P3,因此變成狀態2,P3被移到最前

      狀態3表示,這次訪問的數據頁不存在于鏈表,所以需要在BP中新申請一個數據頁Px,加到鏈表頭。但由于內存已滿,不能申請新內存。于是清空鏈表末尾Pm數據頁內存,存入Px的內容,放到鏈表頭部

      最終就是最久沒有被訪問的數據頁Pm被淘汰。

      若此時要做一個全表掃描,會咋樣?若要掃描一個200G的表,而這個表是一個歷史數據表,平時沒有業務訪問它。

      那么,按此算法掃描,就會把當前BP里的數據全部淘汰,存入掃描過程中訪問到的數據頁的內容。也就是說BP里主要放的是這個歷史數據表的數據。

      對于一個正在做業務服務的庫,這可不行呀。你會看到,BP內存命中率急劇下降,磁盤壓力增加,SQL語句響應變慢。

      所以,InnoDB不能直接使用原始的LRU。InnoDB對其進行了優化。

      改進的LRU算法

      InnoDB按5:3比例把鏈表分成New區和Old區。圖中LRU_old指向的就是old區域的第一個位置,是整個鏈表的5/8處。即靠近鏈表頭部的5/8是New區域,靠近鏈表尾部的3/8是old區域。

      改進后的LRU算法執行流程:

      狀態1,要訪問P3,由于P3在New區,和優化前LRU一樣,將其移到鏈表頭部 =》狀態2

      之后要訪問一個新的不存在于當前鏈表的數據頁,這時依然是淘汰掉數據頁Pm,但新插入的數據頁Px,是放在LRU_old處

      處于old區的數據頁,每次被訪問的時候都要做如下判斷:

      若該數據頁在LRU鏈表中存在的時間超過1s,就把它移動到鏈表頭部

      MySQL數據查詢太多會OOM嗎?

      若該數據頁在LRU鏈表中存在的時間短于1s,位置保持不變。1s是由參數innodb_old_blocks_time控制,默認值1000,單位ms。

      該策略,就是為了處理類似全表掃描的操作量身定制。還是掃描200G歷史數據表:

      4. 掃描過程中,需要新插入的數據頁,都被放到old區域

      5. 一個數據頁里面有多條記錄,這個數據頁會被多次訪問到,但由于是順序掃描,這個數據頁第一次被訪問和最后一次被訪問的時間間隔不會超過1秒,因此還是會被保留在old區域

      6. 再繼續掃描后續的數據,之前的這個數據頁之后也不會再被訪問到,于是始終沒有機會移到鏈表頭部(New區),很快就會被淘汰出去。

      可以看到,這個策略最大的收益,就是在掃描這個大表的過程中,雖然也用到了BP,但對young區完全沒有影響,從而保證了Buffer Pool響應正常業務的查詢命中率。

      小結

      MySQL采用的是邊算邊發的邏輯,因此對于數據量很大的查詢結果來說,不會在server端保存完整的結果集。所以,如果客戶端讀結果不及時,會堵住MySQL的查詢過程,但是不會把內存打爆。

      而對于InnoDB引擎內部,由于有淘汰策略,大查詢也不會導致內存暴漲。并且,由于InnoDB對LRU算法做了改進,冷數據的全表掃描,對Buffer Pool的影響也能做到可控。

      全表掃描還是比較耗費IO資源的,所以業務高峰期還是不能直接在線上主庫執行全表掃描的。

      MySQL 數據結構

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:張小白帶你玩轉Atlas 200DK+CANN 5.0.4+MindStudio 3.0.4(11)番外:TF卡擴容
      下一篇:鯤鵬彈性云服務器開發環境搭建
      相關文章
      亚洲av中文无码乱人伦在线r▽ | 亚洲国产香蕉碰碰人人| 亚洲高清无码专区视频| 亚洲s码欧洲m码吹潮| 亚洲第一街区偷拍街拍| 亚洲欧美日韩综合俺去了| 亚洲日本在线电影| 亚洲国产无线乱码在线观看| 亚洲日韩精品无码AV海量| 亚洲另类无码专区首页| 亚洲爆乳少妇无码激情| 老子影院午夜伦不卡亚洲| 亚洲第一区在线观看| 国产精品亚洲产品一区二区三区| 久久久久一级精品亚洲国产成人综合AV区| 亚洲AV之男人的天堂| 国产自偷亚洲精品页65页| 亚洲日本乱码在线观看| 亚洲爆乳无码一区二区三区| 亚洲av永久无码精品秋霞电影影院 | 亚洲色av性色在线观无码| 中文字幕亚洲综合久久| 亚洲黄色在线观看| 亚洲一线产区二线产区精华| jlzzjlzz亚洲jzjzjz| 亚洲国产精品无码久久98| 一本色道久久88亚洲综合 | 亚洲av日韩av永久无码电影| 日日摸日日碰夜夜爽亚洲| 亚洲精品国精品久久99热| 色噜噜亚洲精品中文字幕| 亚洲av无码不卡一区二区三区| 少妇中文字幕乱码亚洲影视| 亚洲大码熟女在线观看| 亚洲AV无码专区电影在线观看| 国产亚洲精品国看不卡| 国外亚洲成AV人片在线观看| yy6080亚洲一级理论| 亚洲色婷婷综合久久| 亚洲综合自拍成人| 久久久久亚洲国产|