天天看點

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

作者:Java牛逼架構師

一丶Mysql整體架構#

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

MySQL 可以分為 Server 層和存儲引擎層兩部分

1.Server 層#

Server 層包括連接配接器、查詢緩存、分析器、優化器、執行器等,涵蓋 MySQL 的大多數核心服務功能,以及所有的内置函數(如日期、時間、數學和加密函數等),所有跨存儲引擎的功能都在這一層實作,比如存儲過程、觸發器、視圖等。

  • 連接配接器
  • 連接配接器負責跟用戶端建立連接配接、擷取權限、維持和管理連接配接
  • 查詢緩存
  • 對于查詢語句,mysql server層會将查詢語句和對應的結果,使用key - value的緩存結構進行緩存,但是一旦發生更新,那麼查詢緩存就需要失效。是以查詢緩存在高版本的mysql中已經被移除
  • 分析器
  • 分析器先會做“詞法分析”。你輸入的是由多個字元串和空格組成的一條 SQL 語句,MySQL 需要識别出裡面的字元串分别是什麼,代表什麼。
  • 然後做“文法分析”。根據詞法分析的結果,文法分析器會根據文法規則,判斷你輸入的這個 SQL 語句是否滿足 MySQL 文法。
  • 優化器
  • 優化器是在表裡面有多個索引的時候,決定使用哪個索引。或者在一個語句有多表關聯(join)的時候,決定各個表的連接配接順序。
  • 執行器
  • MySQL 通過分析器知道了你要做什麼,通過優化器知道了該怎麼做,于是就進入了執行器階段,開始執行語句。開始執行的時候,要先判斷一下你對這個表 T 有沒有執行查詢的權限,如果沒有,就會傳回沒有權限的錯誤,如果有權限,就打開表繼續執行。打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口。

2.存儲引擎層#

存儲引擎層負責資料的存儲和提取。其架構模式是插件式的,支援 InnoDB、MyISAM、Memory 等多個存儲引擎。現在最常用的存儲引擎是 InnoDB,它從 MySQL 5.5.5 版本開始成為了預設存儲引擎。下面我們對比兩個常用的存儲引擎

MyISAM InnoDB
存儲結構 Myisam 建立表後生成的檔案有三個,分别為: frm:建立表的語句 MYD:表裡面的資料檔案(myisam data) MYI:表裡面的索引檔案(myisam index) Innodb 建立表後生成的檔案有兩個,分别為: frm:建立表的語句 idb:表裡面的資料+索引檔案
索引 非聚集索引,MyISAM 是非聚集索引,資料檔案是分離的,索引儲存的是資料檔案的指針。主鍵索引和輔助索引是獨立的。 聚集索引,聚集索引的檔案存放在主鍵索引的葉子節點上
事務支援 不提供事務支援 提供事務支援
鎖的粒度 隻支援表級鎖 支援行級鎖。
存儲表的具體行數 儲存表的總行數,如果select count() from table;會直接取出出該值。 沒有儲存表的總行數,如果使用select count(*) from table;就會周遊整個表,消耗相當大,但是在加了wehre條件後,myisam和innodb處理的方式都一樣。
以下的分析針對 update t set a='1' where 主鍵 = 1這條語句
           
深入解讀MySQL InnoDB存儲引擎Update語句執行過程

二丶開啟事務#

在mysql中,無論使用者是否手動開啟一個事務,sql都是在一個事務中進行的。我們可以使用start transaction開啟一個事務,commit送出事務,rollback復原事務。

預設情況下,mysql存在自動送出(autocommit=1),這時候即使我們沒有顯式開啟事務,直接執行update語句,那麼mysql會隐式的開啟一個事務,并在這條update執行結束後自動送出

如果set autocommit = 0 或者顯式開啟了一個事務,那麼update執行結束後不會自動送出,而是需要手動發起commit 或者rollback

無論式顯式事務,還是隐式事務,mysql都會在事務内部第一次執行增删改操作的時候,給事務配置設定一個事務号

三丶Sql解析,查詢計劃生成#

mysql伺服器層會從連接配接中讀取sql語句,然後進行詞法解析,文法解析,查詢優化(為什麼update語句需要查詢優化?不查出來怎麼知道修改哪些行資料昵)最終生成一個AST樹,這便是實體執行計劃,執行器會根據執行計劃,調用存儲引擎的接口,

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

四丶查詢需要修改的資料#

Mysql InnoDB Buffer Pool

Mysql索引(究極無敵細節版

mysql innodb存儲引擎對磁盤的讀取是以頁為機關的,為了避免每次都從磁盤讀取資料,innodb存在buffer pool使用LRU連結清單維護最近通路到的頁,為了更快的從buffer pool中查找到目标頁,innodb 還使用表空間号和頁号作為key,頁作為value,形成Hash表。如果我們目标頁已經在buffer pool中那麼直接傳回目标頁,如果不在那麼需要進行磁盤io加載目标頁到記憶體,然後緩存到buffer pool中

1.buffer pool是如何維護頁在記憶體中的#

buffer poo中存在三個關鍵的連結清單結構

  • Free List 空閑連結清單,連結清單中将空閑緩沖頁的控制塊(控制塊中記錄了緩沖頁的位置)進行串聯,用于管理未被使用的緩沖池空間
  • LRU List,最近最少使用連結清單,利用LRU算法在buffer pool滿後淘汰冷門資料頁(innodb 為了應對預讀,全表掃描,對應LRU連結清單進行了改進,分成young區,和old區,解決預讀:innodb規定當磁盤某個頁面在初次加載到buffer pool中某個緩沖頁時,該緩沖頁對應的控制塊會放在old區域的頭部,這樣預讀到的且後續如果不進行後續通路的頁面會逐漸從old區移除,而不影響young區使用頻率高的緩沖頁。,解決全表掃描:nnodb規定對于某個處于old區的緩沖頁第一次通路時,就在其控制塊中記錄下通路時間,如果後續通路的時間和第一次通路的時間,在某個時間通路間隔内(innodb_old_blocks_time可以進行設定)那麼頁面不會從old區移動到young區,反之移動到young區中。這個時間間隔預設時1000ms,基本上多次通路同一個頁面中的多個記錄的時間不會超過1s,解決熱門資料經常需要移動到LRU鍊頭部的問題: innodb規定隻有被通路的緩沖頁位于young區的前1/4範圍外,才會進行移動,是以前1/4的高熱度的資料,不會頻繁移動)
  • Flush List,髒鍊,維護在buffer pool中進行了修改,後續需要重新整理到磁盤的緩沖頁資訊,innodb修改後的頁不會立即刷盤,而是使用Flush list記錄,背景存線上程定時進行刷髒

2.怎麼從16k的頁中找到目标資料#

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

結合B+樹索引結構,執行引擎根據頁号找到根節點,然後根據根節點中的索引資料進行比較,找到子節點,重複此過程直至找到葉子節點所在的頁。

到了葉子節點所在的頁後,根據葉子節點頁中的Page Dictionary中的槽找到目标記錄所在的組,然後周遊這一組中的記錄,找到目标記錄。如果是範圍查詢,還需要根據B+樹葉子節點間的雙向指針繼續查找,直到找到不符合要求的記錄位置。(為了避免我們在周遊B+樹的時候,其他線程修改了B+樹的結構,此過程還需要對B+樹進行加闩鎖)(詳細可看 Mysql索引(究極無敵細節版中的InnoDB索引方案一節)

五丶檢驗鎖和加鎖#

Mysql 鎖

1.Mysql中的鎖#

  • 中繼資料鎖MDL,mysql伺服器層的MDL主要是避免操作資料的同時存在另外線程修改表結構,實作二者的互斥
  • innodb表鎖
    • 表級S鎖,X鎖
    • 使用Lock Tables t Read,innodb存儲引擎會對表t加共享鎖
    • 使用Lock tables t write,innodb存儲引擎會對表t加獨占鎖
    • 表級意向鎖:
    • innodb存儲引擎中,當對表中某些記錄加S鎖之前,會在表上加上一個IS鎖,同樣加X鎖之前會加表級IX鎖,這裡的I表示意向鎖,S or X表示共享還是互斥,表級意向鎖存在的目的是後續對表加S鎖,X鎖的時候,可以快速判斷表中是否存在加鎖的記錄,避免周遊每一個記錄檢視是否被加鎖。
  • innodb 行鎖
    • Record Lock
  • 官方名稱Lock_REC_NOT_GAP
  • 記錄鎖有S鎖和X鎖,S型記錄鎖之間可以共享,X型記錄鎖和S型記錄鎖,X型記錄鎖互斥
    • GAP Lock
  • innodb的可重複讀級别,使用詞鎖解決幻讀問題,前面我們說過,其難點在于,加鎖的時候幻影記錄還未出現。官方使用Lock_GAP實作如下操作
  • 此處的gap鎖可以反之其他事務在number為8記錄前面的間隙插入新的記錄,在區間(3,8)内無法進行插入操作,當另外一個事務要插入number為4的記錄時,首先需要定位到該條記錄的下一條記錄,也就是number為8的記錄,此時number為8的記錄具備gap鎖,是以将阻塞插入操作,直到gap鎖被釋放,其他事務才能進行插入。

    gap鎖出現的目的,就是為了防止插入幻影記錄,如果對記錄上gap鎖,并不會限制其他事務對記錄加記錄鎖。

  • innodb有兩個虛拟的記錄Infimum(虛拟最小),Supermun(虛拟最大)當我們想在(xx,正無窮)範圍鎖住幻影記錄時就可以對Supermun加gap鎖。
    • Next-Key Lock
  • Next-Key Lock = 記錄鎖 + gap鎖,既鎖住記錄,也鎖住記錄之前的間隙
    • Insert Intention Lock#
  • 插入意向鎖,表示事務想在某個間隙插入新的記錄,但是目前處于等待狀态。
  • 比如事務A持有(4,8)範圍内的gap鎖,事務B和C,想插入(4,8)範圍内的記錄,就會在記憶體中生成事務B,C對應的插入意向鎖,目前事務A釋放gap鎖的時候,将喚醒事務B和C,事務B和C可以同時擷取插入意向鎖,然後進行插入。插入意向鎖并不會阻止對記錄繼續上鎖。
    • 隐式鎖
  • 為事務生成記憶體中的鎖結構并不是一個0成本的事情,為了節省這個成本,提出隐式鎖的概念。
  • 當一個事務插入語一條記錄A,其他事務
  • select xxx Lock in share mode讀取記錄A(擷取記錄A的S鎖),或者使用select xxx for update(擷取記錄A的X鎖)
  • 立即修改記錄A(擷取x鎖)
  • 對于聚簇索引來說,有一個隐藏列trx_id此列存儲着最後更改記錄的事務id,在目前事務A插入記錄後,便是存儲着目前事務A的id,其他事務B企圖擷取x鎖,s鎖的時候,就需要下先看一下,trx_id隐藏列對應的事務是否存活,如果不是那麼正常擷取,反之需要為目前事務A建立一個x鎖記憶體結構,并标記is_waiting為false,然後事務B将為自己建立一個鎖結構,is_waiting 為true然後事務B進入等待狀态
  • 對于二級索引來說,其不具備隐藏列trx_id但是在二級索引頁面的page header中的page_maxt_trx_id屬性,記錄了改動頁面最大的事務id,如果其屬性值小于目前最小的活躍事務id,那麼說明對頁面的改動事務已經送出,否則需要定位到二級索引記錄,然後回表對聚簇索引進行上述聚簇索引的操作。
  • 一個事務對新插入的記錄不需要顯示的加鎖,由于事務id的存在相當于加了一個隐式鎖,别的事務需要加S鎖或者X鎖的時候,先幫之前的事務生成鎖結構,然後為自己生成鎖結構,再進入阻塞狀态。隐式鎖起到了延遲加鎖的作用,也許别的事務不會擷取于隐式鎖沖突的鎖,這時候可以減少記憶體中生成鎖結構。

2.一條Update語句涉及的鎖#

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

2.1加共享中繼資料鎖#

為了避免目前事務操作的時候,存在另外的使用者對目前表進行DDL操作,mysql首先會為目前操作的表加共享中繼資料鎖。這個過程可能存在阻塞的可能,如果目前事務企圖加共享中繼資料鎖的時候,存在另外一個事務正在對表進行DDL操作,這時候另外一個事務上了互斥中繼資料鎖,這時候會出現目前事務阻塞的情況

2.2 加表級意向互斥鎖#

此階段也可能存在阻塞,但是由于innodb支援行鎖,基本上很少有人給表上鎖。如果執行目前事務之前存在另外一個事務給表上了表記共享鎖,表記互斥鎖,那麼目前操作也會被阻塞。

加表記意向鎖的好處在于,若沒有意向鎖,那麼其他事務對表加鎖的時候,需要周遊表中所有記錄確定目前行中的記錄沒有被上鎖

2.3 行鎖#

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

innodb中的行鎖,其實是在記憶體中,為目前行生成一個鎖結構,記錄事務id,索引資訊,鎖資訊,鎖類型等.如果目前事務加鎖的時候,記錄并沒有加鎖,那麼會生成一個鎖結構存儲于記憶體中。如果鎖已經被占用那麼會挂起目前事務,直到鎖被釋放後喚醒目前事務。

六丶修改資料和生成日志#

在成功上鎖之後,就可以放心的更新資料了,innodb将寫三部分内容

1.寫緩沖頁#

  • 如果修改前後這行資料的大小完全沒有發生改變,每一個字段所占用的大小和之前一樣,那麼進行就地更新
  • 但凡存在任何一個字段的大小發生了改變,那麼删除舊記錄,将舊紀錄放入頁的垃圾連結清單中,插入新的記錄

不進行需要修改sql中指定的字段,還需要更新trx_id=目前事務的id,roll_pointer = 指向undo log

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

buffer pool中髒頁的刷盤依賴于背景定時任務線程定時進行重新整理,如果修改到此為止将存在資料丢失的問題,為此innodb存儲引擎還需要寫入以下兩種日志

2.寫undo log#

Mysql InnoDB多版本并發控制MVCC

undo log是為了記錄行資料修改前的結果,用于復原和mvcc。undo log 可以分為兩種——記錄insert undo log,和 update/delete undo log,生成的undo log會寫入到undo log buffer。

  • insert undo log 如何幫助復原剛insert的一行資料
  • insert undo log實際上記錄了插入行資料的主鍵,復原是隻需要根據主鍵進行删除即可
  • update/delete undo log怎麼復原update/delete的一行資料
  • update/delete操作的復原需要記錄操作前資料的完整資訊
  • update/delete undo log中的trx_id,roll_pointer是為了支援mvcc,并且還需要記錄修改删除前後的列資訊,便于復原恢複記錄

2.1.mvcc#

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

如圖多個版本的資料,在undo log中進行了記錄,并且使用roll_pointer,進行串聯,形成版本鍊。快照讀查詢語句執行前,或者使用start transaction with consistent snapshot(立即生成read view)會生成一個read view(一緻性視圖,如下)

深入解讀MySQL InnoDB存儲引擎Update語句執行過程

read view包含如下幾個字段

  • m_ids:在生成read view時,目前系統中活躍的讀寫事務id清單
  • min_trx_id:生成read view時,目前系統中活躍的讀寫事務中最小事務id,也就是m_ids中的最小值
  • max_trx_id:生成read view時,系統應該配置設定給下一個事務的事務id值
  • creator_trx_id:生成該read view的事務的事務id

2.2如何利用一緻性視圖判斷資料是否可見#

  1. 如果被通路版本的trx_id和creator_trx_id相同,意味着目前事務在通路自己修改的記錄,自然可見
  2. 如果通路版本的trx_id屬性值小于read view中的min_trx_id 表明此版本是生成read view之前已經送出的事務,那麼自然可見
  3. 如果通路版本的trx_id,大于等于read view中的max_trx_id說明,目前版本資料是生成read view後開啟事務産生的,那麼自然不可見
  4. 如果通路版本的trx_id 介于min_trx_id和max_trx_id之間,需要判斷trx_id是否位于m_ids清單中,如果在說明建立read view時生成該版本的事務還是活躍的,那麼該版本,不可被通路,如果不在說明建立read view 時生成該版本的事務已經送出,可以被通路到

2.3Read Committed和 Repeatable Read的不同#

  • Read Committed——每次讀取資料前都生成一個Read View
  • 這樣可以保證生成Read view 中的m_ids是實時活躍事務id集合,也許第一次讀取的時候事務A沒送出,其id位于m_ids中,但是第二次讀取的時候事務A送出了,事務A将不位于m_ids中,這樣在第二次讀取的時候,通過m_ids判斷事務A是否送出的時候,可以得到事務A已經送出了,然後讓事務A版本産生的資料可見(見2.2.4中的内容)。
  • Repeatable Read——如果使用begin開啟事務那麼在第一次查詢的時候生成Read view,如果使用start transaction with consistent snapshot那麼執行的時候就會生成read view
  • 這樣可以保證目前事務從頭到尾都是read view中記錄的内容是一緻的,第一次讀取的時候事務A沒有送出,那麼不可見,但是第二次讀取的時候事務A送出了,但是read view的m_ids 和max_trx_id可以判斷事務A不可見,比如事務A事務id小于max_trx_id意味着生成read view是事務A啟動但是沒送出,即使第二次讀事務A送出了,但是m_ids中還是包含事務A,那麼不可見。如果事務A事務id大于max_trx_id,那麼自然第二次還是大于max_trx_id,也是不可見的,進而實作了可重複讀。

3.寫redo log#

Mysql InnoDB Redo log

redo log 記錄事務修改了哪個表空間(space id屬性),哪個頁(page number 屬性),修改後的值(data屬性)

即使是非常簡單的一條變更sql,往往涉及到多出的改動,比如需改sql資料的字元數發生了變更,需要先删除,後插入。并且需要對上一條行記錄的next_record 屬性進行修改,頁中行資料的修改,往往同樣需要修改page header,page dictionary等内容,并且可能伴随着B+樹節點分裂和合并。為了解決存在多種不同修改的問題,innodb存在多種類型的redo log。

3.1 mini-transaction#

innodb 把一次變更分為多個mini-transaction(MTR)一個MTR包含一組redo log,這一組redo log以一個特殊類型的redo log作為類型,恢複的時候,這一組redo log具備原子性,隻有檢測到特殊類型的redo log才任何一組redo log是完整的才會進行恢複(B+樹葉子節點的分裂,不能說分裂一半)

3.2 log buffer#

生成redo log,會寫入到log buffer,log buffer是一塊連續的記憶體空間,由一個個大小為512B的log block組成,預設16mb大小。生成的redo log會找最小的一個redo log block 順序寫入

深入解讀MySQL InnoDB存儲引擎Update語句執行過程
  • buf_next_to_write 标記redo log已經落盤的位置
  • buf_free 是标記buffer pool 剩下的空閑空間

3.3 redo log 刷盤的時機:#

  1. 事務送出
  2. log buffer 空間低于50%
  3. 背景線程周期性刷盤
  4. mysql服務正常關閉
  5. 做checkpoint

3.4 redo log 進行崩潰恢複#

從checkpoint_lsn位置開始讀取redo log,來恢複髒頁和undo log,然後通過undo log把所有未送出的事務的髒頁進行復原

七丶本地送出#

送出階段 innodb存儲引擎需要落盤redo log,mysql伺服器層需要落盤binlog

1.binlog#

二進制邏輯日志,在邏輯備份和主備複制中發揮重要作用,具備三種格式

  • statement
  • 每一條會修改資料的 SQL 都會記錄在 binlog 中。
  • Statement 模式隻記錄執行的 SQL,不需要記錄每一行資料的變化,是以極大的減少了 binlog 的日志量,避免了大量的 IO 操作,提升了系統的性能。
  • 但是,正是由于 Statement 模式隻記錄 SQL,而如果一些 SQL 中包含了函數,那麼可能會出現執行結果不一緻的情況。比如說 uuid() 函數,每次執行的時候都會生成一個随機字元串,在 master 中記錄了 uuid,當同步到 slave 之後,再次執行,就擷取到另外一個結果了。
  • 是以使用 Statement 格式會出現一些資料一緻性問題。
  • row
  • Row 格式不記錄 SQL 語句上下文相關資訊,僅僅隻需要記錄某一條記錄被修改成什麼樣子了。
  • Row 格式的日志内容會非常清楚的記錄下每一行資料修改的細節,這樣就不會出現 Statement 中存在的那種資料無法被正常複制的情況。
  • 不過 Row 格式也有一個很大的問題,那就是日志量太大了,特别是批量 update、整表 delete、alter 表等操作,由于要記錄每一行資料的變化,此時會産生大量的日志,大量的日志也會帶來 IO 性能問題
  • mixed
  • Row 格式不記錄 SQL 語句上下文相關資訊,僅僅隻需要記錄某一條記錄被修改成什麼樣子了。
  • Row 格式的日志内容會非常清楚的記錄下每一行資料修改的細節,這樣就不會出現 Statement 中存在的那種資料無法被正常複制的情況。
  • 不過 Row 格式也有一個很大的問題,那就是日志量太大了,特别是批量 update、整表 delete、alter 表等操作,由于要記錄每一行資料的變化,此時會産生大量的日志,大量的日志也會帶來 IO 性能問題

2.怎麼保證binlog 和redo log狀态一緻#

mysql采用了内部XA事務的機制保證binlog,和redo log的狀态順序一緻,通過兩階段送出的方式實作,兩階段送出存在一個協調者和多個參與者,在mysql中binlog是協調者,redo log是參與者

2.1mysql的兩階段送出#

  1. prepare階段innodb刷redo log到磁盤,redo log刷盤完成後,修改事務狀态為TRX_PREPAREDprepare如果失敗,那麼事務會復原,而prepare成功那麼進入commit階段
  2. commit階段mysql伺服器層寫入binlog,寫入完成後,修改事務狀态為TRX_NOT_STARTED,表示事務已經成功送出

2.2當機的處理#

  • 事務轉換若為TRX_ACTIVE那麼復原事務
  • 事務狀态為TRX_NOT_STARTED 那麼說明redo log 和binlog都成功落盤,這時候任務事務已經送出
  • 恢複的時候如果發現事務狀态為TRX_PREPARED,根據binlog的狀态判斷是送出還是復原。若binlog 寫入失敗,那麼復原若binlog寫入成功那麼送出并修改事務為TRX_NOT_STARTED

3.redo log 和binlog 實體落盤政策#

3.1 innodb_flush_log_at_trx_commit空置redo log的落盤#

  • 0表示每秒進行一次重新整理
  • 1表示每次事務送出時落盤
  • 2表示每次事務送出都隻寫redolog緩沖寫道作業系統緩存中,由作業系統決定何時刷盤

3.2 sync_binlog控制binlog的落盤#

  • 0 表示當事務送出之後,MySQL不做fsync之類的磁盤同步指令重新整理binlog_cache中的資訊到磁盤,而讓Filesystem自行決定什麼時候來做同步,或者cache滿了之後才同步到磁盤。
  • n表示當每進行n次事務送出之後,MySQL将進行一次fsync之類的磁盤同步指令來将binlog_cache中的資料強制寫入磁盤。
  • 1表示每次事務送出都刷盤

八丶主備複制#

主庫寫入binlog之後,備庫的io線程會讀取主庫的binlog,并轉存為本地的中繼日志relay log,備庫上的sql線程讀取relay log并在本地執行

1.主備複制的政策#

  • 異步複制:主庫寫完binlog後即可傳回送出成功,無需等待備庫響應
  • 半同步複制:主庫接受指定數量的備機轉儲relay log成功的ACK後可傳回送出成功(還支援逾時時間,逾時沒有傳回那麼主庫傳回成功)
  • 同步複制:主庫等備庫回放relay log執行完,事務之後才能傳回送出成功

不同的政策,其性能和一緻性要求不同,也影響到主庫能否傳回

九丶傳回送出成功#

至此mysql會給用戶端傳回成功

十丶髒頁刷盤#

innodb背景有專門的線程負責将buffer pool中的髒頁重新整理到磁盤

  • 從LRU連結清單中的冷資料重新整理一部分頁面到磁盤
  • 背景線程定時從LRU連結清單尾部掃描一些頁面,掃描的頁面數量可以通過innodb_lru_scan_depth指定,如果在LRU中發現髒頁,那麼重新整理到磁盤
  • 從flush連結清單重新整理一部分頁面到磁盤
  • 背景線程也會定時從flush連結清單中重新整理一部分頁面到磁盤,重新整理速率取決于系統是否繁忙

如果背景線程重新整理的很慢,且有新的頁面需要進行緩存,這時候會從LRU連結清單尾部看看是否有可以直接釋放的非髒頁,如果不存在那麼需要刷盤然後緩存新的頁。

原文連結:https://www.chuangkit.com/designtools/designindex