33 我查這麼多資料,會不會把資料庫記憶體打爆?

2021-10-03 10:24:33 字數 4050 閱讀 7912

假如有如下全表掃瞄:

mysql -h$host -p$port -u$user -p$pwd -e "select * from db1.t" > $target_file
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,如果 socket send buffer 被寫滿,就會暫停讀資料的流程。

net_buffer_length 的最大值是 1g,這個值比 socket send buffer大(一般是幾m)

也就是說,mysql是「邊讀邊發」的,如果客戶端接收得慢,或者socket_receive_buffer ,堵住了,會導致客戶端的結果發不出去,這個事務的執行時間就會變長。

如下是客戶端不去讀 socket receive buffer 中的內容,然後在服務端 show processlist 看到的結果:

如果你看到 state 的值一直處於「sending to client」,就表示伺服器端的網路棧寫滿了。

對於正常的線上業務來說,如果乙個查詢的返回結果不會很多的話,我都建議你使用 mysql_store_result 這個介面,直接把查詢結果儲存到本地記憶體。

mysql 裡看到很多個執行緒都處於「sending to client」這個狀態,可以這樣處理:

評估業務,是否有必要返回這麼多結果

把net_buffer_length引數設定大一點。

與「sending to client」長相很類似的乙個狀態是「sending data」。

乙個查詢語句的狀態變化:

mysql 查詢語句進入執行階段後,首先把狀態設定成「sending data」;

然後,傳送執行結果的列相關的資訊(meta data) 給客戶端;

再繼續執行語句的流程;

執行完成後,把狀態設定成空字串。

也就是說,「sending data」並不一定是指「正在傳送資料」,而可能是處於執行器過程中的任意階段。比如,乙個鎖等待的場景,就能看到 sending data 狀態。

比如:

我在mysql5.5版本看到的狀態是這樣的:

state的值是statistics,不知道是不是一樣的。

總結:就是說,僅當乙個執行緒處於「等待客戶端接收結果」的狀態,才會顯示"sending to client";而如果顯示成「sending data」,它的意思只是「正在執行」。

innodb 記憶體的乙個作用,是儲存更新的結果,再配合 redo log,就避免了隨機寫盤。

記憶體的資料頁是在 buffer pool (bp) 中管理的,在 wal 裡 buffer pool 起到了加速更新的作用。而實際上,buffer pool 還有乙個更重要的作用,就是加速查詢。

如果此時有乙個查詢,可以直接在記憶體裡拿結果,不用讀磁碟,所以buffer pool 還有加速查詢的作用,加速查詢的乙個重要指標就是:記憶體命中率。

show engine innodb status
innodb buffer pool 的大小是由引數 innodb_buffer_pool_size 確定的,一般建議設定成可用物理記憶體的 60%~80%。

如果乙個 buffer pool 滿了,而又要從磁碟讀入乙個資料頁,那肯定是要淘汰乙個舊資料頁的,有如下兩種演算法:

這個演算法的核心就是淘汰最久未使用的資料。

innodb 管理 buffer pool 的 lru 演算法,是用鍊錶來實現的。

鍊錶頭部是 p1,表示 p1 是最近剛剛被訪問過的資料頁;

假設記憶體裡只能放下這麼多資料頁;這時候有乙個讀請求訪問 p3,因此變成狀態 2,p3 被移到最前面;

狀態 3 表示,這次訪問的資料頁是不存在於鍊錶中的,所以需要在 buffer pool 中新申請乙個資料頁 px,加到鍊錶頭部。但是由於記憶體已經滿了,不能申請新的記憶體。於是,會清空鍊錶末尾 pm 這個資料頁的記憶體,存入 px 的內容,然後放到鍊錶頭部。

從效果上看,就是最久沒有被訪問的資料頁 pm,被淘汰了。

缺點:假設按照這個演算法,我們要掃瞄乙個 200g 的表,而這個表是乙個歷史資料表,平時沒有業務訪問它。那麼,按照這個演算法掃瞄的話,就會把當前的 buffer pool 裡的資料全部淘汰掉,存入掃瞄過程中訪問到的資料頁的內容。也就是說 buffer pool 裡面主要放的是這個歷史資料表的資料。

此時buffer pool 的記憶體命中率急劇下降,磁碟壓力增加,sql 語句響應變慢。

在 innodb 實現上,按照 5:3 的比例把整個 lru 鍊錶分成了 young 區域和 old 區域。圖中 lru_old 指向的就是 old 區域的第乙個位置,是整個鍊錶的 5/8 處。也就是說,靠近鍊錶頭部的 5/8 是 young 區域,靠近鍊錶尾部的 3/8 是 old 區域。

狀態 1,要訪問資料頁 p3,由於 p3 在 young 區域,因此和優化前的 lru 演算法一樣,將其移到鍊錶頭部,變成狀態 2。

之後要訪問乙個新的不存在於當前鍊錶的資料頁,這時候依然是淘汰掉資料頁 pm,但是新插入的資料頁 px,是放在 lru_old 處。

處於 old 區域的資料頁,每次被訪問的時候都要做下面這個判斷:

所以整個流程簡單為:不在鍊錶資料頁,先放在old處,如果在old處呆的超過1秒了,移到young這裡的頭部,如果資料一直在young處,移到young的前面。

針對全表掃瞄的200g資料,

掃瞄過程中,需要新插入的資料頁,都被放到 old 區域 ;

乙個資料頁裡面有多條記錄,這個資料頁會被多次訪問到,但由於是順序掃瞄,這個資料頁第一次被訪問和最後一次被訪問的時間間隔不會超過 1 秒,因此還是會被保留在 old 區域;

再繼續掃瞄後續的資料,之前的這個資料頁之後也不會再被訪問到,於是始終沒有機會移到鍊錶頭部(也就是 young 區域),很快就會被淘汰出去。

小結:由於 mysql 採用的是邊算邊發的邏輯,因此對於資料量很大的查詢結果來說,不會在 server 端儲存完整的結果集。所以,如果客戶端讀結果不及時,會堵住 mysql 的查詢過程,但是不會把記憶體打爆。

疑問:如果可以一次性放入net_buffer, 對於執行器來說是不是意味著「全都寫出去了」,也就不會有 sending to client 狀態,體現出 sending to client 的狀態就是網路棧 socket receive buffer 寫滿了。

net_buffer 應該是針對每個請求執行緒單獨分配的。

innodb改進的lru演算法,如果遇到連續兩次的全表掃瞄,會把young區的3/5給覆蓋掉了,因為兩次掃瞄時間間隔會超過一秒?

bugku 這麼多資料報

看到之後有點懵逼 然後用wireshark 然後只想到了 http過濾 然後發現不對 然後參考其他人的部落格 經大佬提示,一般 getshell 流的 tcp 的 報文中很可能包含 command 這個字段,我們可以通過 協議 contains 內 容 來查詢 getshell 流 通過追蹤 tcp...

軟體這麼多,我還是愛這款

在本期文章中,筆者將使用guitar pro7和大家惡補一下對於吉他手來說特別特別難的特殊和弦問題。在之前的內容裡我們曾經說到過李榮浩老師的 耳朵 這首歌,在這首歌裡面我們運用到了cmaj7 gmaj7這些特殊和弦。如果認真練習過的同學肯定都可以按得出相應的和弦,然而,當我要把這首歌從g key公升...

你沒有見過這麼多的「我愛XX網」

http www.52vista.com vista 愛好者之家 www.52hardware.com 52硬體 www.52samsung.com 52三星手機 www.52mobiles.com 52手機 http www.52shouji.com 52手機批發 www.52dm.cn 52動漫...