華為桌面云虛擬機(jī)
Windows Server 2019
版本1809
OS內(nèi)部版本17763.107
Windows Server 2019服務(wù)器 一天之中內(nèi)存利用率越來(lái)越高直到死機(jī),不斷擴(kuò)容內(nèi)存從10G到16G一重啟就正常然后慢慢又循環(huán),連續(xù)幾天都這樣,疑似內(nèi)存泄漏
內(nèi)存泄漏是什么?
內(nèi)存泄漏指由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存。內(nèi)存泄漏并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)計(jì)錯(cuò)誤,導(dǎo)致在釋放該段內(nèi)存之前就失去了對(duì)該段內(nèi)存的控制,從而造成了內(nèi)存的浪費(fèi)。內(nèi)存泄漏通常情況下只能由獲得程序源代碼的程序員才能分析出來(lái)。
一周內(nèi)內(nèi)存利用率zabbix監(jiān)控圖:
1.用PoolMonX工具,查看發(fā)現(xiàn) Tag 為“toke”的進(jìn)程占用內(nèi)存巨大,標(biāo)為綠色,即處于上升狀態(tài)
findstr /s /m /l “toke” c:/windows/system32/drivers/*.sys 通過(guò)findstr查找未發(fā)現(xiàn)有效信息
(未解決本案例故障)
2.網(wǎng)上說(shuō)Win10發(fā)現(xiàn)微軟自帶拼音輸入法會(huì)引起這個(gè)問(wèn)題,底層導(dǎo)致此問(wèn)題的進(jìn)程是TextServiceFramework,一輸入漢字就會(huì)觸發(fā),可能某些軟件通過(guò)taskhostw.exe來(lái)提交內(nèi)存。而taskhostw.exe的已提交內(nèi)存并沒(méi)有被任務(wù)管理器正確的顯示出來(lái)。這就導(dǎo)致一些已提交內(nèi)存被隱藏起來(lái)了。有人測(cè)試,新建一個(gè)文本文檔,共輸入了9千個(gè)漢字之后(我輸入的“水”字,按了十多分鐘的s鍵),已提交內(nèi)存從2G,一路漲到上限9.9G,然后windows提示內(nèi)存不足(設(shè)置了虛擬內(nèi)存上限為2g,去掉顯卡占用的,已提交內(nèi)存上限是9.9G)整個(gè)過(guò)程中,使用中內(nèi)存基本保持不變。關(guān)閉文本文檔后,已提交內(nèi)存下降為6.5g。不僅內(nèi)存占用不正常,而且一半都無(wú)法釋放。結(jié)束taskhostw.exe進(jìn)程,被輸入中文所占用的內(nèi)存全部釋放,不過(guò)中文輸入法就用不了了。什么也不做,光打9千個(gè)漢字,就能爆掉8G內(nèi)存。 (本案例未有這個(gè)情況,未解決)
3.殺毒,如Svchost.exe病毒
當(dāng)發(fā)現(xiàn)Svchost.exe不在%systemroot%\System32目錄中的,可以安全刪除,同時(shí)在注冊(cè)表中查找對(duì)應(yīng)的注冊(cè)項(xiàng)刪除掉。 或用 tasklist/svc命令查看svchost.exe 右邊的服務(wù)是不是“暫缺”,是的話為中毒了。
Svchost.exe在%systemroot%\System32目錄,說(shuō)明Svchost.exe是被病毒感染了,可以用殺毒軟件清除(本案例未中毒,未解決)
4.卸載多余軟件,360安全,虛擬機(jī)插件等等,排除軟件沖突(本案例未有這個(gè)情況,未解決)
5.在系統(tǒng)日志文件里仔發(fā)現(xiàn)一個(gè)報(bào)錯(cuò):服務(wù)器無(wú)法通過(guò)系統(tǒng)非頁(yè)面共享區(qū)來(lái)進(jìn)行分配,因?yàn)楣蚕韰^(qū)當(dāng)前是空的,事件ID2019(本案例未有這個(gè)情況,未解決)
6.修改注冊(cè)表而使內(nèi)存占用達(dá)到一個(gè)較低程度的時(shí)候自動(dòng)去整理內(nèi)存,系統(tǒng)默認(rèn)是內(nèi)存達(dá)到80%時(shí)整理內(nèi)存,可以手工修改注冊(cè)表
打開regedit
找到
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management
右鍵“編輯”—添加DWORD值 數(shù)值名稱:PoolUsageMaximum 數(shù)據(jù)類型:REG_DWORD
基數(shù):十進(jìn)制 數(shù)值數(shù)據(jù):50
#通知內(nèi)存管理器在達(dá)到PagedPoolMax的50%的時(shí)候開始自動(dòng)整理,這里可以根據(jù)系統(tǒng)配置自行設(shè)置閥值
找到如果存在的話無(wú)需再創(chuàng)建 數(shù)值名稱:PagedPoolSize 數(shù)據(jù)類型:REG_DWORD
基數(shù):十六進(jìn)制 數(shù)值數(shù)據(jù):0xFFFFFFFF #為計(jì)算機(jī)分配最大的頁(yè)面緩沖池已取代其他資源
重新啟動(dòng)服務(wù)器生效 (本案例解決)
作者|碼海
來(lái)源|碼海(ID:seaofcode)
每當(dāng)我們執(zhí)行某個(gè) SQL 發(fā)現(xiàn)很慢時(shí),都會(huì)下意識(shí)地反應(yīng)是否加了索引,那么大家是否有想過(guò)加了索引為啥會(huì)使數(shù)據(jù)查找更快呢,索引的底層一般又是用什么結(jié)構(gòu)存儲(chǔ)的呢,相信大家看了標(biāo)題已經(jīng)有答案了,沒(méi)錯(cuò)!B+樹!那么它相對(duì)于一般的鏈表,哈希等有何不同,為何多數(shù)存儲(chǔ)引擎都選擇使用它呢,今天我就來(lái)揭開 B+ 樹的面紗,相信看了此文,B+ 樹不再神秘,對(duì)你理解以下高頻面試題會(huì)大有幫助!
為啥索引常用 B+ 樹作為底層的數(shù)據(jù)結(jié)構(gòu)
除了 B+ 樹索引,你還知道什么索引
為啥推薦自增 id 作為主鍵,自建主鍵不行嗎
什么是頁(yè)分裂,頁(yè)合并
怎么根據(jù)索引查找行記錄
本文將會(huì)從以下幾個(gè)方面來(lái)講解 B+ 樹
定義問(wèn)題
幾種常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)對(duì)比
頁(yè)分裂與頁(yè)合并
要知道索引底層為啥使用 B+ 樹,得看它解決了什么問(wèn)題,我們可以想想,日常我們用到的比較多的 SQL 有哪些呢。
假設(shè)我們有一張以下的用戶表:
CREATE TABLE `user` (
`id` int(11) unsigned NOT AUTO_INCREMENT,
`name` varchar(20) DEFAULT COMMENT '姓名',
`idcard` varchar(20) DEFAULT COMMENT '身份證號(hào)碼',
`age` tinyint(10) DEFAULT COMMENT '年齡',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶信息';
一般我們會(huì)有如下需求:
1、根據(jù)用戶 id 查用戶信息
select * from user where id=123;
2、根據(jù)區(qū)間值來(lái)查找用戶信息
select * from user where id > 123 and id < 234;
3、按 id 逆序排列,分頁(yè)取出用戶信息
select * from user where id < 1234 order by id desc limit 10;
從以上的幾個(gè)常用 SQL 我們可以看到索引所用的數(shù)據(jù)結(jié)構(gòu)必須滿足以下三個(gè)條件
根據(jù)某個(gè)值精確快速查找
根據(jù)區(qū)間值的上下限來(lái)快速查找此區(qū)間的數(shù)據(jù)
索引值需要排好序,并支持快速順序查找和逆序查找
接下來(lái)我們以主鍵索引(id 索引)為例來(lái)看看如何用相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來(lái)構(gòu)造它
接下來(lái)我們想想有哪些數(shù)據(jù)結(jié)構(gòu)滿足以上的條件
1、散列表
散列表(也稱哈希表)是根據(jù)關(guān)鍵碼值(Key value)而直接進(jìn)行訪問(wèn)的數(shù)據(jù)結(jié)構(gòu),它讓碼值經(jīng)過(guò)哈希函數(shù)的轉(zhuǎn)換映射到散列表對(duì)應(yīng)的位置上,查找效率非常高。哈希索引就是基于散列表實(shí)現(xiàn)的,假設(shè)我們對(duì)名字建立了哈希索引,則查找過(guò)程如下圖所示:
對(duì)于每一行數(shù)據(jù),存儲(chǔ)引擎都會(huì)對(duì)所有的索引列(上圖中的 name 列)計(jì)算一個(gè)哈希碼(上圖散列表的位置),散列表里的每個(gè)元素指向數(shù)據(jù)行的指針,由于索引自身只存儲(chǔ)對(duì)應(yīng)的哈希值,所以索引的結(jié)構(gòu)十分緊湊,這讓哈希索引查找速度非??欤〉枪K饕灿兴牧觿?shì),如下:
針對(duì)哈希索引,只有精確匹配索引所有列的查詢才有效,比如我在列(A,B)上建立了哈希索引,如果只查詢數(shù)據(jù)列 A,則無(wú)法使用該索引。
哈希索引并不是按照索引值順序存存儲(chǔ)的,所以也就無(wú)法用于排序,也就是說(shuō)無(wú)法根據(jù)區(qū)間快速查找
哈希索引只包含哈希值和行指針,不存儲(chǔ)字段值,所以不能使用索引中的值來(lái)避免讀取行,不過(guò),由于哈希索引多數(shù)是在內(nèi)存中完成的,大部分情況下這一點(diǎn)不是問(wèn)題
哈希索引只支持等值比較查詢,包括=,IN,不支持任何范圍的查找,如 age > 17
綜上所述,哈希索引只適用于特定場(chǎng)合, 如果用得對(duì),確實(shí)能再帶來(lái)很大的性能提升,如在 InnoDB 引擎中,有一種特殊的功能叫「自適應(yīng)哈希索引」,如果 InnoDB 注意到某些索引列值被頻繁使用時(shí),它會(huì)在內(nèi)存基于 B+ 樹索引之上再創(chuàng)建一個(gè)哈希索引,這樣就能讓 B+樹也具有哈希索引的優(yōu)點(diǎn),比如快速的哈希查找。
2、鏈表
雙向鏈表支持順序查找和逆序查找,如圖下
但顯然不支持我們說(shuō)的按某個(gè)值或區(qū)間的快速查找,另外我們知道表中的數(shù)據(jù)是要不斷增加的,索引也是要及時(shí)插入更新的,鏈表顯然也不支持?jǐn)?shù)據(jù)的快速插入,所以能否在鏈表的基礎(chǔ)上改造一下,讓它支持快速查找,更新,刪除。有一種結(jié)構(gòu)剛好能滿足我們的需求,這里引入跳表的概念。
什么是跳表?簡(jiǎn)單地說(shuō),跳表是在鏈表之上加上多層索引構(gòu)成的。如下圖所示
假設(shè)我們現(xiàn)在要查找區(qū)間 7- 13 的記錄,再也不用從頭開始查找了,只要在上圖中的二級(jí)索引開始找即可,遍歷三次即可找到鏈表的區(qū)間位置,時(shí)間復(fù)雜度是 O(logn),非常快,這樣看來(lái),跳表是能滿足我們的需求的,實(shí)際上它的結(jié)構(gòu)已經(jīng)和 B+ 樹非常接近了,只不過(guò) B+ 樹是從平衡二叉查找樹演化而來(lái)的而已,接下來(lái)我們一步步來(lái)看下如何將平衡二叉查找樹改造成 B+ 樹。
先來(lái)看看什么是平衡二叉查找樹,平衡二叉查找樹具有如下性質(zhì):
若左子樹不空,則左子樹上所有節(jié)點(diǎn)的值均小于它的根節(jié)點(diǎn)的值;
若右子樹不空,則右子樹上所有節(jié)點(diǎn)的值均大于或等于它的根節(jié)點(diǎn)的值;
每個(gè)非葉子節(jié)點(diǎn)的左右子樹的高度之差的絕對(duì)值(平衡因子)最多為1。
下圖就是一顆平衡二叉查找樹
從其特性就可以看到平衡二叉查找樹查找節(jié)點(diǎn)的時(shí)間復(fù)雜度是 O(log2n)
現(xiàn)在我們將其改造成 B+ 樹
可以看到主要區(qū)別就是所有的節(jié)點(diǎn)值都在最后葉節(jié)點(diǎn)上用雙向鏈表連接在了一起,仔細(xì)和跳表對(duì)比一下 ,是不是很像,現(xiàn)在如果我們要找15 ~ 27 這個(gè)區(qū)間的數(shù)只要先找到 15 這個(gè)節(jié)點(diǎn)(時(shí)間復(fù)雜度 logn=3 次)再?gòu)那巴蟊闅v直到 27 這個(gè)節(jié)點(diǎn)即可,即可找到這區(qū)間的節(jié)點(diǎn),這樣它完美地支持了我們提的三個(gè)需求:快速查找值,區(qū)間,順序逆序查找。
假設(shè)有 1 億個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)要查詢多少次呢,顯然最多為 log21億=27 次,如果這 1 億個(gè)節(jié)點(diǎn)都在內(nèi)存里,那 27 次顯然不是問(wèn)題,可以說(shuō)是非??炝?,但一個(gè)新的問(wèn)題出現(xiàn)了,這 1 億個(gè)節(jié)點(diǎn)在內(nèi)存大小是多少呢,我們簡(jiǎn)單算一下,假設(shè)每個(gè)節(jié)點(diǎn) 16 byte,則 1 億個(gè)節(jié)點(diǎn)大概要占用 1.5G 內(nèi)存!對(duì)于內(nèi)存這么寶貴的資源來(lái)說(shuō)是非常可怕的空間消耗,這還只是一個(gè)索引,一般我們都會(huì)在表中定義多個(gè)索引,或者庫(kù)中定義多張表,這樣的話內(nèi)存很快就爆滿了!所以在內(nèi)存中完全裝載一個(gè) B+ 樹索引顯然是有問(wèn)題的,如何解決呢。
內(nèi)存放不下, 我們可以把它放到磁盤嘛,磁盤空間比內(nèi)存大多了,但新的問(wèn)題又來(lái)了,我們知道內(nèi)存與磁盤的讀取速度相差太大了,通常內(nèi)存是納秒級(jí)的,而磁盤是毫秒級(jí)的,讀取同樣大小的數(shù)據(jù),兩者可能相差上萬(wàn)倍,于是上一步我們計(jì)算的 27 次查詢?nèi)绻旁诖疟P中來(lái)看就非常要命了(查找一個(gè)節(jié)點(diǎn)可以認(rèn)為是一次磁盤 IO,也就是說(shuō)有 27 次磁盤 IO!),27 次查詢是否可以優(yōu)化?
可以很明顯地觀察到查詢次數(shù)和樹高有關(guān),那樹高和什么有關(guān),很明顯和每個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)個(gè)數(shù)有關(guān),即 N 叉樹中的 N,假設(shè)現(xiàn)在有 16 個(gè)數(shù),我們分別用二叉樹和五叉樹來(lái)構(gòu)建,看下樹高分別是多少
可以看到如果用二叉樹 ,要遍歷 5 個(gè)節(jié)點(diǎn),如果用五叉樹 ,只要遍歷 3 次,一下少了兩次磁盤 IO,回過(guò)頭來(lái)看 上文的一億個(gè)節(jié)點(diǎn),如果我們用 100 叉樹來(lái)構(gòu)建,需要幾次 IO 呢
可以看到,最多遍歷五次(實(shí)際上根節(jié)點(diǎn)一般存在內(nèi)存里的,所以可以認(rèn)為是 4 次)!磁盤 IO 一下從 27 減少到了 5!性能可以說(shuō)是大大提升了,有人說(shuō) 5 次還是太多,是不是可以把 100 叉樹改成 1000 或 10000 叉樹呢,這樣 IO 次數(shù)不就就能進(jìn)一步減少了。
這里我們就需要了解頁(yè)(page)的概念,在計(jì)算機(jī)里,無(wú)論是內(nèi)存還是磁盤,操作系統(tǒng)都是按頁(yè)的大小進(jìn)行讀取的(頁(yè)大小通常為 4 kb),磁盤每次讀取都會(huì)預(yù)讀,會(huì)提前將連續(xù)的數(shù)據(jù)讀入內(nèi)存中,這樣就避免了多次 IO,這就是計(jì)算機(jī)中有名的局部性原理,即我用到一塊數(shù)據(jù),很大可能這塊數(shù)據(jù)附近的數(shù)據(jù)也會(huì)被用到,干脆一起加載,省得多次 IO 拖慢速度, 這個(gè)連續(xù)數(shù)據(jù)有多大呢,必須是操作系統(tǒng)頁(yè)大小的整數(shù)倍,這個(gè)連續(xù)數(shù)據(jù)就是 MySQL 的頁(yè),默認(rèn)值為 16 KB,也就是說(shuō)對(duì)于 B+ 樹的節(jié)點(diǎn),最好設(shè)置成頁(yè)的大?。?6 KB),這樣一個(gè) B+ 樹上的節(jié)點(diǎn)就只會(huì)有一次 IO 讀。
那有人就會(huì)問(wèn)了,這個(gè)頁(yè)大小是不是越大越好呢,設(shè)置大一點(diǎn),節(jié)點(diǎn)可容納的數(shù)據(jù)就越多,樹高越小,IO 不就越小了嗎,這里要注意,頁(yè)大小并不是越大越好,InnoDB 是通過(guò)內(nèi)存中的緩存池(pool buffer)來(lái)管理從磁盤中讀取的頁(yè)數(shù)據(jù)的。頁(yè)太大的話,很快就把這個(gè)緩存池?fù)螡M了,可能會(huì)造成頁(yè)在內(nèi)存與磁盤間頻繁換入換出,影響性能。
通過(guò)以上分析,相信我們不難猜測(cè)出 N 叉樹中的 N 該怎么設(shè)置了,只要選的時(shí)候盡量保證每個(gè)節(jié)點(diǎn)的大小等于一個(gè)頁(yè)(16kb)的大小即可。
現(xiàn)在我們來(lái)看看開頭的問(wèn)題, 為啥推薦自增 id 作為主鍵,自建主鍵不行嗎,有人可能會(huì)說(shuō)用戶的身份證是唯一的,可以用它來(lái)做主鍵,假設(shè)以身份證作主鍵,會(huì)有什么問(wèn)題呢。
B+ 樹為了維護(hù)索引的有序性,每插入或更新一條記錄的時(shí)候,會(huì)對(duì)索引進(jìn)行更新。假設(shè)原來(lái)基于身份證作索引的 B+ 樹如下(假設(shè)為二叉樹 ,圖中只列出了身份證的前四位)
現(xiàn)在有一個(gè)開頭是 3604 的身份證對(duì)應(yīng)的記錄插入 db ,此時(shí)要更新索引,按排序來(lái)更新的話,顯然這個(gè) 3604 的身份證號(hào)應(yīng)該插到左邊節(jié)點(diǎn) 3504 后面(如下圖示,假設(shè)為二叉樹)。
如果把 3604 這個(gè)身份證號(hào)插入到 3504 后面的話,這個(gè)節(jié)點(diǎn)的元素個(gè)數(shù)就有 3 個(gè)了,顯然不符合二叉樹的條件,此時(shí)就會(huì)造成頁(yè)分裂,就需要調(diào)整這個(gè)節(jié)點(diǎn)以讓它符合二叉樹的條件。
如圖示:調(diào)整過(guò)后符合二叉樹條件
這種由于頁(yè)分裂造成的調(diào)整必然導(dǎo)致性能的下降,尤其是以身份證作為主鍵的話,由于身份證的隨機(jī)性,必然造成大量的隨機(jī)結(jié)點(diǎn)中的插入,進(jìn)而造成大量的頁(yè)分裂,進(jìn)而造成性能的急劇下降,那如果是以自增 id 作為主鍵呢,由于新插入的表中生成的 id 比索引中所有的值都大,所以它要么合到已存在的節(jié)點(diǎn)(元素個(gè)數(shù)未滿)中,要么放入新建的節(jié)點(diǎn)中(如下圖示)所以如果是以自增 id 作為主鍵,就不存在頁(yè)分裂的問(wèn)題了,推薦!
有頁(yè)分裂就必然有頁(yè)合并,什么時(shí)候會(huì)發(fā)生頁(yè)合并呢,當(dāng)刪除表記錄的時(shí)候,索引也要?jiǎng)h除,此時(shí)就有可能發(fā)生頁(yè)合并,如圖示:
當(dāng)我們刪除 id 為 7,9 對(duì)應(yīng)行的時(shí)候,上圖中的索引就要更新,把 7,9 刪掉,此時(shí) 8,10 就應(yīng)該合到一個(gè)節(jié)點(diǎn),不然 8,10 分散在兩個(gè)節(jié)點(diǎn)上,可能造成兩次 IO 讀,勢(shì)必會(huì)影響查找效率! 那什么時(shí)候會(huì)發(fā)生頁(yè)合并呢,我們可以定個(gè)閾值,比如對(duì)于 N 叉樹來(lái)說(shuō),當(dāng)節(jié)點(diǎn)的個(gè)數(shù)小于 N/2 的時(shí)候就應(yīng)該和附近的節(jié)點(diǎn)合并,不過(guò)需要注意的是合并后節(jié)點(diǎn)里的元素大小可能會(huì)超過(guò) N,造成頁(yè)分裂,需要再對(duì)父節(jié)點(diǎn)等進(jìn)行調(diào)整以讓它滿足 N 叉樹的條件。
相信大家看完以上的 B+ 樹索引的介紹應(yīng)該還有個(gè)疑惑,怎么根據(jù)對(duì)應(yīng)的索引值查找行記錄呢,其實(shí)相應(yīng)的行記錄就放在最后的葉子節(jié)點(diǎn)中,找到了索引值,也就找到了行記錄。如圖示:
可以看到,非葉子節(jié)點(diǎn)只存了索引值,只在最后一行才存放了行記錄,這樣極大地減小了索引了大小,而且只要找到索引值就找到了行記錄,也提升了效率,
這種在葉節(jié)點(diǎn)存放一整行記錄的索引被稱為聚簇索引,其他的就稱為非聚簇索引。
綜上所述,B+樹有以下特點(diǎn):
每個(gè)節(jié)點(diǎn)中子節(jié)點(diǎn)的個(gè)數(shù)不能超過(guò) N,也不能小于 N/2(不然會(huì)造成頁(yè)分裂或頁(yè)合并)
根節(jié)點(diǎn)的子節(jié)點(diǎn)個(gè)數(shù)可以不超過(guò) m/2,這是一個(gè)例外
m 叉樹只存儲(chǔ)索引,并不真正存儲(chǔ)數(shù)據(jù),只有最后一行的葉子節(jié)點(diǎn)存儲(chǔ)行數(shù)據(jù)。
通過(guò)鏈表將葉子節(jié)點(diǎn)串聯(lián)在一起,這樣可以方便按區(qū)間查找
本文由日常中常用的 SQL 由淺入深地總結(jié)了 B+ 樹的特點(diǎn),相信大家應(yīng)該對(duì) B+ 樹索引有了比較清晰地認(rèn)識(shí),所以說(shuō)為啥我們要掌握底層原來(lái),學(xué)完了 B+ 樹,再看開頭提的幾個(gè)問(wèn)題,其實(shí)也不過(guò)如此,深挖底層,有時(shí)候確實(shí)能讓你以不變應(yīng)萬(wàn)變。
?智能穿戴市場(chǎng)群雄逐鹿,OPPO入局能沒(méi)有殺手锏?
?半小時(shí)訓(xùn)練億級(jí)規(guī)模知識(shí)圖譜,亞馬遜AI開源知識(shí)圖譜嵌入表示框架DGL-KE
?“出道” 5 年采用率達(dá) 78%,Kubernetes 的成功秘訣是什么?