mysql内存淘汰_从创建索引过程中内存变化来看SQL Server与MySQL的内存淘汰算法
在sqlserver中,幾年之前就注意到一個(gè)現(xiàn)象:sqlserver中對(duì)一個(gè)大表創(chuàng)建索引或者rebuild索引的過(guò)程中,會(huì)引起內(nèi)存劇烈的動(dòng)蕩,究其原因?yàn)楹?#xff0c;這種現(xiàn)象到底正不正常,是不是sqlserver內(nèi)存管理存在缺陷?
另外,最近剛好想到跟MySQL對(duì)比一下類似操作引起的內(nèi)存變化,測(cè)試MySQL會(huì)不會(huì)有類似問(wèn)題,這里就簡(jiǎn)單寫(xiě)個(gè)代碼驗(yàn)證一下這個(gè)問(wèn)題。
數(shù)據(jù)庫(kù)是一個(gè)非常依賴內(nèi)存資源的軟件系統(tǒng),通過(guò)緩存數(shù)據(jù)(索引)到內(nèi)存中,來(lái)改善數(shù)據(jù)物理訪問(wèn)的性能問(wèn)題,
但是內(nèi)存往往又不是無(wú)限大,或者足以容納所有相關(guān)數(shù)據(jù)的容量,因此就存在內(nèi)存頁(yè)面的淘汰問(wèn)題。
內(nèi)存頁(yè)的淘汰算法,多數(shù)是遵循LRU算法,LRU是Least Recently Used的縮寫(xiě),也即遵循“最近做少使用”的原則,選擇最近最久未使用的頁(yè)面予以淘汰。
這個(gè)算法表面上看起來(lái)沒(méi)什么問(wèn)題,如果有注意觀察過(guò)在一臺(tái)相對(duì)穩(wěn)定的服務(wù)器上,給大表創(chuàng)建索引的過(guò)程,就會(huì)發(fā)現(xiàn),整個(gè)過(guò)程中,buffer pool會(huì)發(fā)生劇烈的動(dòng)蕩,創(chuàng)建索引的表會(huì)迅速侵入內(nèi)存,擠走內(nèi)存中原本的緩存。
由于SQLServer作為商業(yè)數(shù)據(jù)庫(kù),有關(guān)于它的頁(yè)面淘汰算法的研究較少,僅僅是指導(dǎo)一個(gè)大概是遵循LRU的原則的,但是有沒(méi)有在LRU的基礎(chǔ)上進(jìn)行改進(jìn)或者優(yōu)化,就不得而知,
但是SQLServer究竟有沒(méi)有對(duì)該問(wèn)題做改進(jìn)或者優(yōu)化?這里從一個(gè)索引的創(chuàng)建來(lái)管中窺豹,從側(cè)面驗(yàn)證一下這個(gè)算法。
這里需要借助SQLServer中的一個(gè)變量值:Page life expectancy,
相信稍微熟悉SQLServer一點(diǎn)的人應(yīng)該都知道這個(gè)參數(shù)代表的意義:內(nèi)存頁(yè)面的平均滯留時(shí)間,如果內(nèi)存頁(yè)面不斷地被置換出去,這個(gè)值將會(huì)維持不變或者變得更小,因?yàn)樾螺d入內(nèi)存的頁(yè)面在內(nèi)存中停留的時(shí)間是較短的。
不知道有沒(méi)有人注意過(guò),在一臺(tái)內(nèi)存相對(duì)穩(wěn)定的服務(wù)器上,對(duì)大表(1000W+)創(chuàng)建索引的時(shí)候,Page life expectancy這個(gè)變量值會(huì)急轉(zhuǎn)直下,這說(shuō)明了什么?
大表創(chuàng)建索引粗略講是讀數(shù)據(jù),然后寫(xiě)數(shù)據(jù)(索引樹(shù))的過(guò)程,這個(gè)過(guò)程中必然將相關(guān)的表讀入內(nèi)存,那么讀入內(nèi)存之后,他有沒(méi)有淘汰內(nèi)存中已有的數(shù)據(jù)?|
如果有,這明顯是不合理的,創(chuàng)建索引只是創(chuàng)建索引,目的不是把內(nèi)存中已有的熱數(shù)據(jù)擠走,但是它還真的給內(nèi)存中已有的熱數(shù)據(jù)給擠走了。
反觀MySQL(Innodb引擎),Redis等數(shù)據(jù)庫(kù),都是基于優(yōu)化的LRU或者LFU的原則淘汰頁(yè)面。
MySQL甚至可以人為地去調(diào)整這個(gè)LFU算法的一些參數(shù)值(innodb_old_blocks_pct,innodb_old_blocks_time),來(lái)達(dá)到優(yōu)化內(nèi)存淘汰的目的。
MySQL中雖然沒(méi)有類似于PLE的參數(shù),但是可以從其他參數(shù)來(lái)間接推斷,如果發(fā)生同樣的操作,相關(guān)的表會(huì)不會(huì)擠走內(nèi)存中的熱數(shù)據(jù).
這里基于MySQL information_schema.innodb_buffer_pool_stats這張表來(lái)作分析,其中這個(gè)表有兩個(gè)字段,pages_made_young, pages_not_made_young ,這兩個(gè)的變化代表這個(gè)新進(jìn)入內(nèi)存中的頁(yè)面冷熱變化情況。
同樣的道理,如果內(nèi)存中充斥著大量的熱點(diǎn)數(shù)據(jù),在對(duì)一個(gè)大表創(chuàng)建索引的過(guò)程中,并不希望因?yàn)閯?chuàng)建索引而把熱點(diǎn)數(shù)據(jù)擠出內(nèi)存,究竟是不是這樣的,同樣在創(chuàng)建索引的過(guò)程中,觀察一下這兩個(gè)值的變化情況就可以了。
測(cè)試方法
這里通過(guò)循環(huán),以5秒為間隔,連續(xù)輸出sqlserver中的Page life expectancy這個(gè)變量的值,以及MySQL中的pages_made_young和pages_not_made_young。
#coding=utf-8
importthreadingimportpymssqlimportpymysqlfrom time importctime,sleepimportdatetimeimporttime
mssql_conn_conf= {'host': '***.***.***.***', 'port': 1433, 'db': 'master'}
mysql_conn_conf= {'host': '***.***.***.***', 'port': 3306, 'user': 'root', 'password': '***', 'db': 'information_schema'}defmssql_ple():
conn= pymssql.connect(host=mssql_conn_conf['host'], port=mssql_conn_conf['port'], database=mssql_conn_conf['db'])
cursor=conn.cursor()try:
cursor.execute("select cntr_value from sys.dm_os_performance_counters where object_name = 'MSSQL$SQL2014:Buffer Manager' and counter_name = 'Page life expectancy'")
row=cursor.fetchone()print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+ '------>'+str(row[0]))exceptpymssql.Error as e:print("mysql execute error:", e)
cursor.close()
conn.close()defmysql_memory():
conn= pymysql.connect(host=mysql_conn_conf['host'], port=mysql_conn_conf['port'], database=mysql_conn_conf['db'],user=mysql_conn_conf['user'],password = mysql_conn_conf['password'])
cursor=conn.cursor()try:
cursor.execute('''SELECT
SUM(pages_made_young) AS total_pages_made_young,
SUM(pages_not_made_young) AS total_pages_not_made_young
FROM
(
SELECT pages_made_young, pages_not_made_young
FROM information_schema.innodb_buffer_pool_stats
)t;''')
row=cursor.fetchone()print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+ '------>'+'made_young:'+str(row[0])+'not_made_young:'+str(row[1]))exceptpymssql.Error as e:print("mysql execute error:", e)
cursor.close()
conn.close()if __name__ == '__main__':while 1>0:
mysql_memory()
time.sleep(5)
SQLServer中的PLE變化測(cè)試
其實(shí)很容易觀察,對(duì)于一臺(tái)沒(méi)有負(fù)載的服務(wù)器,因?yàn)闆](méi)有新的內(nèi)存頁(yè)面載入內(nèi)存,它的Page life expectancy值是遞增的的,這個(gè)變量的單位是秒,間隔一秒,這個(gè)值會(huì)自動(dòng)加1。
一旦有新的頁(yè)面載入內(nèi)存,如果內(nèi)存已經(jīng)被用完,隨著內(nèi)存中已有的頁(yè)面淘汰出去,這個(gè)值是會(huì)自動(dòng)遞減的,或者出現(xiàn)斷崖式的下降。
這里運(yùn)行上述腳本,打印出來(lái)當(dāng)前服務(wù)器的Page life expectancy值,稍等一段時(shí)間后,在某個(gè)大表上創(chuàng)建出一個(gè)索引,再觀察這個(gè)值的變化情況,
step1,對(duì)DB01庫(kù)上的表進(jìn)行反復(fù)的查詢,使其載入內(nèi)存(最近較多使用),左圖是DB01庫(kù)占用的內(nèi)存情況,
step2,在DB02庫(kù)上對(duì)一張大表創(chuàng)建索引,此過(guò)程中中會(huì)發(fā)現(xiàn)創(chuàng)建索引的表會(huì)迅速將已換成的數(shù)據(jù)擠出內(nèi)存
MySQL中的pages_made_young和page_not_made_young測(cè)試
因筆者事前重啟過(guò)實(shí)例,因此made_young的值很小,關(guān)鍵要看,在某個(gè)大表上創(chuàng)建索引的過(guò)程中是不是會(huì)大量的made_young就行了。
這里可以看到,在創(chuàng)建索引開(kāi)始之后,會(huì)出現(xiàn)大量的not_made_young,實(shí)際上這種效果是預(yù)期的,僅僅是創(chuàng)建索引,而不是順帶讓當(dāng)前這個(gè)大表的數(shù)據(jù)擠走熱點(diǎn)數(shù)據(jù)(并沒(méi)有大批量的made_young)
這里也給出在db02上創(chuàng)建索引前后兩個(gè)庫(kù)占用的內(nèi)存情況,雖然db02在其某個(gè)大表上創(chuàng)建索引之后占用了一定量的內(nèi)存,但是這部分內(nèi)存并非熱數(shù)據(jù),是隨時(shí)可以被擠出buffer pool的,因?yàn)樗麄儧](méi)有page_made_young
step1,對(duì)db01庫(kù)上的表進(jìn)行的多次查詢,使其載入內(nèi)存,左圖是db01庫(kù)占用的內(nèi)存情況,
step2,在db02庫(kù)上對(duì)一張大表創(chuàng)建索引,此過(guò)程中中會(huì)發(fā)現(xiàn)不斷地有大量的page_not_made_young,另外原本的db01庫(kù)的內(nèi)存并沒(méi)有被大量的擠出。
總結(jié)
以個(gè)人淺薄的經(jīng)歷以及測(cè)試過(guò)程,發(fā)現(xiàn)sqlserver的內(nèi)存管理,與MySQL相比,這一點(diǎn)做的確實(shí)不到位,與MySQL相比,其buffer pool管理本身的算法就存在問(wèn)題,又是一個(gè)黑盒,也沒(méi)有人為可以調(diào)整的可能性。
sqlserver再不加油,真的就沒(méi)人用了……
總結(jié)
以上是生活随笔為你收集整理的mysql内存淘汰_从创建索引过程中内存变化来看SQL Server与MySQL的内存淘汰算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 均值已知检验方差_21.(6)AB te
- 下一篇: SQLServer控制用户访问权限表