從創建索引過程中記憶體變化來看SQL Server與MySQL的記憶體淘汰演算法

来源:https://www.cnblogs.com/wy123/archive/2020/04/21/12729298.html
-Advertisement-
Play Games

在sqlserver中,幾年之前就註意到一個現象:sqlserver中對一個大表創建索引或者rebuild索引的過程中,會引起記憶體劇烈的動蕩,究其原因為何,這種現象到底正不正常,是不是sqlserver記憶體管理存在缺陷?另外,最近剛好想到跟MySQL對比一下類似操作引起的記憶體變化,測試MySQL會不 ...


在sqlserver中,幾年之前就註意到一個現象:sqlserver中對一個大表創建索引或者rebuild索引的過程中,會引起記憶體劇烈的動蕩,究其原因為何,這種現象到底正不正常,是不是sqlserver記憶體管理存在缺陷?
另外,最近剛好想到跟MySQL對比一下類似操作引起的記憶體變化,測試MySQL會不會有類似問題,這裡就簡單寫個代碼驗證一下這個問題。

 

資料庫是一個非常依賴記憶體資源的軟體系統,通過緩存數據(索引)到記憶體中,來改善數據物理訪問的性能問題,

但是記憶體往往又不是無限大,或者足以容納所有相關數據的容量,因此就存在記憶體頁面的淘汰問題。
記憶體頁的淘汰演算法,多數是遵循LRU演算法,LRU是Least Recently Used的縮寫,也即遵循“最近做少使用”的原則,選擇最近最久未使用的頁面予以淘汰。
這個演算法錶面上看起來沒什麼問題,如果有註意觀察過在一臺相對穩定的伺服器上,給大表創建索引的過程,就會發現,整個過程中,buffer pool會發生劇烈的動蕩,創建索引的表會迅速侵入記憶體,擠走記憶體中原本的緩存。

由於SQLServer作為商業資料庫,有關於它的頁面淘汰演算法的研究較少,僅僅是指導一個大概是遵循LRU的原則的,但是有沒有在LRU的基礎上進行改進或者優化,就不得而知,
但是SQLServer究竟有沒有對該問題做改進或者優化?這裡從一個索引的創建來管中窺豹,從側面驗證一下這個演算法。

這裡需要藉助SQLServer中的一個變數值:Page life expectancy,
相信稍微熟悉SQLServer一點的人應該都知道這個參數代表的意義:記憶體頁面的平均滯留時間,如果記憶體頁面不斷地被置換出去,這個值將會維持不變或者變得更小,因為新載入記憶體的頁面在記憶體中停留的時間是較短的。
不知道有沒有人註意過,在一臺記憶體相對穩定的伺服器上,對大表(1000W+)創建索引的時候,Page life expectancy這個變數值會急轉直下,這說明瞭什麼?
大表創建索引粗略講是讀數據,然後寫數據(索引樹)的過程,這個過程中必然將相關的表讀入記憶體,那麼讀入記憶體之後,他有沒有淘汰記憶體中已有的數據?|
如果有,這明顯是不合理的,創建索引只是創建索引,目的不是把記憶體中已有的熱數據擠走,但是它還真的給記憶體中已有的熱數據給擠走了。

反觀MySQL(Innodb引擎),Redis等資料庫,都是基於優化的LRU或者LFU的原則淘汰頁面。
MySQL甚至可以人為地去調整這個LFU演算法的一些參數值(innodb_old_blocks_pct,innodb_old_blocks_time),來達到優化記憶體淘汰的目的。
MySQL中雖然沒有類似於PLE的參數,但是可以從其他參數來間接推斷,如果發生同樣的操作,相關的表會不會擠走記憶體中的熱數據.
這裡基於MySQL information_schema.innodb_buffer_pool_stats這張表來作分析,其中這個表有兩個欄位,pages_made_young, pages_not_made_young ,這兩個的變化代表這個新進入記憶體中的頁面冷熱變化情況。
同樣的道理,如果記憶體中充斥著大量的熱點數據,在對一個大表創建索引的過程中,並不希望因為創建索引而把熱點數據擠出記憶體,究竟是不是這樣的,同樣在創建索引的過程中,觀察一下這兩個值的變化情況就可以了。

 
測試方法

這裡通過迴圈,以5秒為間隔,連續輸出sqlserver中的Page life expectancy這個變數的值,以及MySQL中的pages_made_young和pages_not_made_young。

#coding=utf-8
import threading
import pymssql
import pymysql
from time import ctime,sleep
import datetime
import time

mssql_conn_conf = {'host': '***.***.***.***', 'port': 1433,  'db': 'master'}
mysql_conn_conf = {'host': '***.***.***.***', 'port': 3306, 'user': 'root', 'password': '***', 'db': 'information_schema'}

def mssql_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]))
    except pymssql.Error as e:
        print("mysql execute error:", e)
    cursor.close()
    conn.close()

def mysql_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]))
    except pymssql.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變化測試

其實很容易觀察,對於一臺沒有負載的伺服器,因為沒有新的記憶體頁面載入記憶體,它的Page life expectancy值是遞增的的,這個變數的單位是秒,間隔一秒,這個值會自動加1。
一旦有新的頁面載入記憶體,如果記憶體已經被用完,隨著記憶體中已有的頁面淘汰出去,這個值是會自動遞減的,或者出現斷崖式的下降。
這裡運行上述腳本,列印出來當前伺服器的Page life expectancy值,稍等一段時間後,在某個大表上創建出一個索引,再觀察這個值的變化情況,

step1,對DB01庫上的表進行反覆的查詢,使其載入記憶體(最近較多使用),左圖是DB01庫占用的記憶體情況,
step2,在DB02庫上對一張大表創建索引,此過程中中會發現創建索引的表會迅速將已換成的數據擠出記憶體

 

MySQL中的pages_made_young和page_not_made_young測試

因筆者事前重啟過實例,因此made_young的值很小,關鍵要看,在某個大表上創建索引的過程中是不是會大量的made_young就行了。
這裡可以看到,在創建索引開始之後,會出現大量的not_made_young,實際上這種效果是預期的,僅僅是創建索引,而不是順帶讓當前這個大表的數據擠走熱點數據(並沒有大批量的made_young)

這裡也給出在db02上創建索引前後兩個庫占用的記憶體情況,雖然db02在其某個大表上創建索引之後占用了一定量的記憶體,但是這部分記憶體並非熱數據,是隨時可以被擠出buffer pool的,因為他們沒有page_made_young
step1,對db01庫上的表進行的多次查詢,使其載入記憶體,左圖是db01庫占用的記憶體情況,
step2,在db02庫上對一張大表創建索引,此過程中中會發現不斷地有大量的page_not_made_young,另外原本的db01庫的記憶體並沒有被大量的擠出。

 

總結
以個人淺薄的經歷以及測試過程,發現sqlserver的記憶體管理,與MySQL相比,一直停留在小學二年級的水平,其buffer pool管理本身的演算法就存在問題,又是一個黑盒,也沒有人為可以調整的可能性。

 


sqlserver再不加油,真的就沒人用了……


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對於剛剛DIY好一臺電腦或者對於目前所用windows系統版本不太滿意的人來說,下載一個官方、純凈的系統鏡像是至關重要的!下麵就來看看如何實現這些操作吧。(所有操作均以Win10為例) 更多精彩文章請關註公眾號『大海的BLOG』 首先說說為什麼官方的系統受人推崇吧: 首先說說第三方系統鏡像: 第一: ...
  • [TOC] 1.docker容器的網路原理圖 2.運行容器為什麼要使用埠映射? 3.docker運行容器埠映射的方法 4.docker自動添加的iptable規則 ...
  • [TOC] 1.docker容器的核心理念 2.運行一個容器 3.docker其他常用命令 3.1.停止容器 3.2.殺死容器 3.3.查看容器列表 3.4.刪除容器 3.5.批量刪除容器 docker rm f 進入容器的目的:排錯,調試 進入容器的方法: docker exec [OPTIONS ...
  • 看第21章時,介紹到瞭解析命令行的神器 getopt,瞭解了 linux 下處理通用命令行的方法。 命令行可分為參數與選項,其中不帶 - 或 -- 首碼的為參數,對一個命令而言數量是固定的,多個參數之間的順序也是固定的(不然命令沒法區分);而選項就是帶 - 或 -- 首碼的,可有可沒有的,由用戶的輸 ...
  • 我們在工作的過程中,有時候會利用源碼包安裝軟體。雖然相對於二進位軟體包,配置和編譯起來繁瑣點,但是它的可移植性卻好得多。 ...
  • 已允許遠程連接 遠程服務已啟動 能ping通 但就是連不上,原因何在? 如果關閉防火牆,你會發現連上了。 在防火牆 》高級設置 》入站規則 里,有如下兩項: 遠程桌面(TCP-In) 公用 已啟用(否) 遠程桌面(TCP-In) 域 專用 已啟用(是) 所連WiFi為歸為公用網路 只需開啟防火牆,啟 ...
  • 1、ls命令 就是 list 的縮寫,通過 ls 命令不僅可以查看 linux 文件夾包含的文件,而且可以查看文件許可權(包括目錄、文件夾、文件許可權)查看目錄信息等等。 常用參數搭配: ls -a 列出目錄所有文件,包含以.開始的隱藏文件 ls -A 列出除.及..的其它文件 ls -r 反序排列 ...
  • 一、nginx版本 開源版本主要有: mainline--含最新特性和及時的bug修複,以及一些試驗性的組件和一些新bug; stable--不含最新特性,但含有重要的bug修複並跟隨移植到相關的mainline版本。推薦生產環境使用。 二、安裝方式: (1)使用預編譯的二進位包:此種方式最快捷; ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...