mysql實戰33 | 我查這么多數據,會不會把數據庫內存打爆?

我經常會被問到這樣一個問題:我的主機內存只有 100G,現在要對一個 200G 的大表做全表掃描,會不會把數據庫主機的內存用光了?

這個問題確實值得擔心,被系統 OOM(out of memory)可不是鬧著玩的。但是,反過來想想,邏輯備份的時候,可不就是做整庫掃描嗎?如果這樣就會把內存吃光,邏輯備份不是早就掛了?

所以說,對大表做全表掃描,看來應該是沒問題的。但是,這個流程到底是怎么樣的呢?

全表掃描對 server 層的影響

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

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

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

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

實際上,服務端并不需要保存一個完整的結果集。取數據和發數據的流程是這樣的:

  1. 獲取一行,寫到 net_buffer 中。這塊內存的大小是由參數 net_buffer_length 定義的,默認是 16k。
  2. 重復獲取行,直到 net_buffer 寫滿,調用網絡接口發出去。
  3. 如果發送成功,就清空 net_buffer,然后繼續取下一行,并寫入 net_buffer。
  4. 如果發送函數返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地網絡棧(socket send buffer)寫滿了,進入等待。直到網絡棧重新可寫,再繼續發送。

這個過程對應的流程圖如下所示。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 1 查詢結果發送流程

從這個流程中,你可以看到:

  1. 一個查詢在發送過程中,占用的 MySQL 內部的內存最大就是 net_buffer_length 這么大,并不會達到 200G;
  2. socket send buffer 也不可能達到 200G(默認定義 /proc/sys/net/core/wmem_default),如果 socket send buffer 被寫滿,就會暫停讀數據的流程。

也就是說,MySQL 是“邊讀邊發的”,這個概念很重要。這就意味著,如果客戶端接收得慢,會導致 MySQL 服務端由于結果發不出去,這個事務的執行時間變長。

比如下面這個狀態,就是我故意讓客戶端不去讀 socket receive buffer 中的內容,然后在服務端 show processlist 看到的結果。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 2 服務端發送阻塞

如果你看到 State 的值一直處于“Sending to client”,就表示服務器端的網絡棧寫滿了。

我在上一篇文章中曾提到,如果客戶端使用–quick 參數,會使用 mysql_use_result 方法。這個方法是讀一行處理一行。你可以想象一下,假設有一個業務的邏輯比較復雜,每讀一行數據以后要處理的邏輯如果很慢,就會導致客戶端要過很久才會去取下一行數據,可能就會出現如圖 2 所示的這種情況。

因此,對于正常的線上業務來說,如果一個查詢的返回結果不會很多的話,我都建議你使用 mysql_store_result 這個接口,直接把查詢結果保存到本地內存。

當然前提是查詢返回結果不多。在第 30 篇文章評論區,有同學說到自己因為執行了一個大查詢導致客戶端占用內存近 20G,這種情況下就需要改用 mysql_use_result 接口了。

另一方面,如果你在自己負責維護的 MySQL 里看到很多個線程都處于“Sending to client”這個狀態,就意味著你要讓業務開發同學優化查詢結果,并評估這么多的返回結果是否合理。

而如果要快速減少處于這個狀態的線程的話,將 net_buffer_length 參數設置為一個更大的值是一個可選方案。

與“Sending to client”長相很類似的一個狀態是“Sending data”,這是一個經常被誤會的問題。有同學問我說,在自己維護的實例上看到很多查詢語句的狀態是“Sending data”,但查看網絡也沒什么問題啊,為什么 Sending data 要這么久?

實際上,一個查詢語句的狀態變化是這樣的(注意:這里,我略去了其他無關的狀態):

  • MySQL 查詢語句進入執行階段后,首先把狀態設置成“Sending data”;
  • 然后,發送執行結果的列相關的信息(meta data) 給客戶端;
  • 再繼續執行語句的流程;
  • 執行完成后,把狀態設置成空字符串。

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


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 3 讀全表被鎖


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 4 Sending data 狀態

可以看到,session B 明顯是在等鎖,狀態顯示為 Sending data。

也就是說,僅當一個線程處于“等待客戶端接收結果”的狀態,才會顯示"Sending to client";而如果顯示成“Sending data”,它的意思只是“正在執行”。

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

在 server 層的處理邏輯我們都清楚了,在 InnoDB 引擎里面又是怎么處理的呢? 掃描全表會不會對引擎系統造成影響呢?

全表掃描對 InnoDB 的影響

在第 2和第 15 篇文章中,我介紹 WAL 機制的時候,和你分析了 InnoDB 內存的一個作用,是保存更新的結果,再配合 redo log,就避免了隨機寫盤。

內存的數據頁是在 Buffer Pool (BP) 中管理的,在 WAL 里 Buffer Pool 起到了加速更新的作用。而實際上,Buffer Pool 還有一個更重要的作用,就是加速查詢。

在第 2 篇文章的評論區有同學問道,由于有 WAL 機制,當事務提交的時候,磁盤上的數據頁是舊的,那如果這時候馬上有一個查詢要來讀這個數據頁,是不是要馬上把 redo log 應用到數據頁呢?

答案是不需要。因為這時候內存數據頁的結果是最新的,直接讀內存頁就可以了。你看,這時候查詢根本不需要讀磁盤,直接從內存拿結果,速度是很快的。所以說,Buffer Pool 還有加速查詢的作用。

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

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

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


? ? ? ? ? ? ? ? ? 圖 5 show engine innodb status 顯示內存命中率

如果所有查詢需要的數據頁都能夠直接從內存得到,那是最好的,對應的命中率就是 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 算法的基本模型。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 6 基本 LRU 算法

InnoDB 管理 Buffer Pool 的 LRU 算法,是用鏈表來實現的。

  1. 在圖 6 的狀態 1 里,鏈表頭部是 P1,表示 P1 是最近剛剛被訪問過的數據頁;假設內存里只能放下這么多數據頁;
  2. 這時候有一個讀請求訪問 P3,因此變成狀態 2,P3 被移到最前面;
  3. 狀態 3 表示,這次訪問的數據頁是不存在于鏈表中的,所以需要在 Buffer Pool 中新申請一個數據頁 Px,加到鏈表頭部。但是由于內存已經滿了,不能申請新的內存。于是,會清空鏈表末尾 Pm 這個數據頁的內存,存入 Px 的內容,然后放到鏈表頭部。
  4. 從效果上看,就是最久沒有被訪問的數據頁 Pm,被淘汰了。

這個算法乍一看上去沒什么問題,但是如果考慮到要做一個全表掃描,會不會有問題呢?

假設按照這個算法,我們要掃描一個 200G 的表,而這個表是一個歷史數據表,平時沒有業務訪問它。

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

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

所以,InnoDB 不能直接使用這個 LRU 算法。實際上,InnoDB 對 LRU 算法做了改進。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 7 改進的 LRU 算法

在 InnoDB 實現上,按照 5:3 的比例把整個 LRU 鏈表分成了 young 區域和 old 區域。圖中 LRU_old 指向的就是 old 區域的第一個位置,是整個鏈表的 5/8 處。也就是說,靠近鏈表頭部的 5/8 是 young 區域,靠近鏈表尾部的 3/8 是 old 區域。

改進后的 LRU 算法執行流程變成了下面這樣。

  1. 圖 7 中狀態 1,要訪問數據頁 P3,由于 P3 在 young 區域,因此和優化前的 LRU 算法一樣,將其移到鏈表頭部,變成狀態 2。
  2. 之后要訪問一個新的不存在于當前鏈表的數據頁,這時候依然是淘汰掉數據頁 Pm,但是新插入的數據頁 Px,是放在 LRU_old 處。
  3. 處于 old 區域的數據頁,每次被訪問的時候都要做下面這個判斷:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?a:若這個數據頁在 LRU 鏈表中存在的時間超過了 1 秒,就把它移動到鏈表頭部;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?b:如果這個數據頁在 LRU 鏈表中存在的時間短于 1 秒,位置保持不變。1 秒這個時間,是由參數? ? ?innodb_old_blocks_time 控制的。其默認值是 1000,單位毫秒。?


這個策略,就是為了處理類似全表掃描的操作量身定制的。還是以剛剛的掃描 200G 的歷史數據表為例,我們看看改進后的 LRU 算法的操作邏輯:

  1. 掃描過程中,需要新插入的數據頁,都被放到 old 區域 ;
  2. 一個數據頁里面有多條記錄,這個數據頁會被多次訪問到,但由于是順序掃描,這個數據頁第一次被訪問和最后一次被訪問的時間間隔不會超過 1 秒,因此還是會被保留在 old 區域;
  3. 再繼續掃描后續的數據,之前的這個數據頁之后也不會再被訪問到,于是始終沒有機會移到鏈表頭部(也就是 young 區域),很快就會被淘汰出去。

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

小結

今天,我用“大查詢會不會把內存用光”這個問題,和你介紹了 MySQL 的查詢結果,發送給客戶端的過程。

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

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

當然,我們前面文章有說過,全表掃描還是比較耗費 IO 資源的,所以業務高峰期還是不能直接在線上主庫執行全表掃描的。

最后,我給你留一個思考題吧。

我在文章中說到,如果由于客戶端壓力太大,遲遲不能接收結果,會導致 MySQL 無法發送結果而影響語句執行。但,這還不是最糟糕的情況。

你可以設想出由于客戶端的性能問題,對數據庫影響更嚴重的例子嗎?或者你是否經歷過這樣的場景?你又是怎么優化的?

你可以把你的經驗和分析寫在留言區,我會在下一篇文章的末尾和你討論這個問題。感謝你的收聽,也歡迎你把這篇文章分享給更多的朋友一起閱讀。

上期問題時間

上期的問題是,如果一個事務被 kill 之后,持續處于回滾狀態,從恢復速度的角度看,你是應該重啟等它執行結束,還是應該強行重啟整個 MySQL 進程。

因為重啟之后該做的回滾動作還是不能少的,所以從恢復速度的角度來說,應該讓它自己結束。

當然,如果這個語句可能會占用別的鎖,或者由于占用 IO 資源過多,從而影響到了別的語句執行的話,就需要先做主備切換,切到新主庫提供服務。

切換之后別的線程都斷開了連接,自動停止執行。接下來還是等它自己執行完成。這個操作屬于我們在文章中說到的,減少系統壓力,加速終止邏輯。

轉載于:https://juejin.im/post/5d05cd43f265da1ba9157c32

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/387572.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/387572.shtml
英文地址,請注明出處:http://en.pswp.cn/news/387572.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[BZOJ2125]最短路

Description 給一個N個點M條邊的連通無向圖,滿足每條邊最多屬于一個環,有Q組詢問,每次詢問兩點之間的最短路徑。 Input 輸入的第一行包含三個整數,分別表示N和M和Q 下接M行,每行三個整數v,u,w表…

Rabbit MQ windows下安裝

Rabbit MQ 是建立在強大的Erlang OTP平臺上,因此安裝Rabbit MQ的前提是安裝Erlang。通過下面兩個連接可以下載安裝最新的版本: 下載并安裝 Eralng OTP For Windows otp_win64_18.3.exe(erlang的環境)運行安裝 Rabbit MQ Serve…

spark集群配置以及java操作spark小demo

spark 安裝配置使用java來操作sparkspark 安裝 tar -zxvf spark-2.4.0-bin-hadoop2.7.tgz rm spark-2.4.0-bin-hadoop2.7.tgz mv spark-2.4.0-bin-hadoop2.7 sparksudo vim /etc/profileexport SPARK_HOME/usr/local/stormexport PATH$PATH:$SPARK_HOME/binsource /etc/profile…

C++筆記(3)——string.h相關的一些小知識

strlen() 用于得到字符數組中第一個\0前的字符的個數&#xff0c;格式如下&#xff1a; strlen(數組); 例子&#xff1a; #include <stdio.h> #include <string.h>int main(){char str[10];gets(str);int len strlen(str);printf("%d\n", len);return 0…

最近發現系統rabbitmq丟消息比較嚴重,于是想了些方案來查找原因,給將消息發送方式添加確認機制。 我們在本地模擬了wms發送打標消息的場景. 1. 有事務 2. 先發點對點隊列, 再發訂

最近發現系統rabbitmq丟消息比較嚴重&#xff0c;于是想了些方案來查找原因&#xff0c;給將消息發送方式添加確認機制。 我們在本地模擬了wms發送打標消息的場景. 1. 有事務 2. 先發點對點隊列, 再發訂閱隊列 3. 批量發送 4. 在生產環境與測試環境的RabbitMQ都進行了測試 …

uoj#388. 【UNR #3】配對樹(線段樹合并)

傳送門 先考慮一個貪心&#xff0c;對于一條邊來說&#xff0c;如果當前這個序列中在它的子樹中的元素個數為奇數個&#xff0c;那么這條邊就會被一組匹配經過&#xff0c;否則就不會 考慮反證法&#xff0c;如果在這條邊兩邊的元素個數都是偶數&#xff0c;那么至少有兩組匹配…

一道Js判斷對象是否相等面試題引發的故事

話說&#xff0c;說什么呢&#xff0c;先看下題吧還是、 function checkName(data) { if (data { name: LIMING }) { console.log("one"); 復制代碼 } else if (data { name: LIMING }) { console.log(two"); 復制代碼 } else { console.log("three&quo…

序列化

什么是序列化&#xff1f;為什么要實現序列化&#xff1f;有什么作用&#xff1f; 序列化就是把具體的對象轉化成二進制的字節碼文件進行存儲或網絡傳輸。反過來就是反序列化。 將要存儲或網絡傳輸的對象必須實現序列化才可以。 如果一個類已經實現了序列…

搭建Hive平臺

http://www.cnblogs.com/gpcuster/archive/2010/02/24/1672635.html Hive是一個基于Hadoop的數據倉庫平臺。通過hive&#xff0c;我們可以方便地進行ETL的工作。hive定義了一個類似于SQL的查詢語言&#xff1a;HQL&#xff0c;能夠將用戶編寫的QL轉化為相應的Mapreduce程序基于…

Java語言與sikuli配合

很早之前寫過一篇介紹sikuli的文章。本文簡單介紹如何在java中使用sikuli進自動化測試。 圖形腳本語言sikuli sikuli IDE可以完成常見的單擊、右擊、移動到、拖動等鼠標操作&#xff0c;java引用sikuli-script.jar同樣可以執行這些常見的鼠標操作&#xff0c;因此即可方便的編寫…

列表生成式,生成器表達式,模塊的使用

三元表達式 無論條件成立與否都要返回一個值, 用于簡化僅有一個判斷的函數(或代碼塊)遞歸 遞歸有循環調用的次數限制,調用函數時,函數相關數據要入棧,而棧區是有限的 二分查找法匿名函數 僅能在定義時使用一次,定義完了就沒了 參數沒有括號,不能有return,會自…

C#怎么用代碼模擬手機去訪問手機網站抓取數據

WebClient client new WebClient ();client.Headers.Add ("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");更改user-agent為手機瀏覽器的。模擬谷歌Android&#xff1a;user-agent"Mozilla/5.0 (Linux; …

angular6 iframe應用

問題一、 iframe如何自適應屏幕高度 解決思路&#xff1a;通過設置iframe外層父元素高度等于window高度&#xff0c;再相對于父元素定位iframe元素&#xff1b;案例如下&#xff1a; 第一步: 模板文件中使用iframe // demo.component.html <div style"position: relati…

jquery下載地址:https://code.jquery.com/jquery/ 影響范圍: 版本低于1.7的jQuery過濾用戶輸入數據所使用的正則表達式存在缺陷,可能導致LOCA

jquery下載地址&#xff1a;https://code.jquery.com/jquery/ 影響范圍&#xff1a; 版本低于1.7的jQuery過濾用戶輸入數據所使用的正則表達式存在缺陷&#xff0c;可能導致LOCATION.HASH跨站漏洞 已測試成功版本&#xff1a; jquery-1.6.min.js&#xff0c;jquery-1.6.1.min…

Myeclipse常用快捷鍵

2019獨角獸企業重金招聘Python工程師標準>>> Ctrl1 快速修復 CtrlD: 刪除當前行 CtrlQ 定位到最后編輯的地方 CtrlL 定位在某行 CtrlO 快速顯示 OutLine CtrlT 快速顯示當前類的繼承結構 CtrlW 關閉當前Editer CtrlK 快速定位到下一個 CtrlE 快速顯示當前Edi…

數字三角形

問題描述 &#xff08;圖&#xff13;.&#xff11;&#xff0d;&#xff11;&#xff09;示出了一個數字三角形。 請編一個程序計算從頂至底的某處的一條路徑&#xff0c;使該路徑所經過的數字的總和最大。●每一步可沿左斜線向下或右斜線向下走&#xff1b;●1&#xff1c;三…

版本低于1.7的jQuery過濾用戶輸入數據所使用的正則表達式存在缺陷

jquery下載地址&#xff1a;https://code.jquery.com/jquery/ 影響范圍&#xff1a; 版本低于1.7的jQuery過濾用戶輸入數據所使用的正則表達式存在缺陷&#xff0c;可能導致LOCATION.HASH跨站漏洞 已測試成功版本&#xff1a; jquery-1.6.min.js&#xff0c;jquery-1.6.1.min.…

RabbitMQ學習總結(6)——消息的路由分發機制詳解

2019獨角獸企業重金招聘Python工程師標準>>> 一、Routing(路由) (using the Java client)在前面的學習中&#xff0c;構建了一個簡單的日志記錄系統&#xff0c;能夠廣播所有的日志給多個接收者&#xff0c;在該部分學習中&#xff0c;將添加一個新的特點&#xff0…

Kaggle爆文:一個框架解決幾乎所有機器學習問題

上周一個叫 Abhishek Thakur 的數據科學家&#xff0c;在他的 Linkedin 發表了一篇文章 Approaching (Almost) Any Machine Learning Problem&#xff0c;介紹他建立的一個自動的機器學習框架&#xff0c;幾乎可以解決任何機器學習問題&#xff0c;項目很快也會發布出來。 這篇…

C# HttpWebRequest GET HTTP HTTPS 請求

這個需求來自于我最近練手的一個項目&#xff0c;在項目中我需要將一些自己發表的和收藏整理的網文集中到一個地方存放&#xff0c;如果全部采用手工操作工作量大而且繁瑣&#xff0c;因此周公決定利用C#來實現。在很多地方都需要驗證用戶身份才可以進行下一步操作&#xff0c;…