InnoDB存儲引擎最早由Innobase Oy公司開發(屬第三方存儲引擎)。從MySQL 5.5版本開始作為表的預設存儲引擎。該存儲引擎是第一個完整支持ACID事務的MySQL存儲引擎,特點是行鎖設計、支持MVCC、支持外鍵、提供一致性非鎖定讀,非常適合OLTP場景的應用使用。目前也是應用最廣泛的... ...
1 InnoDB存儲引擎
InnoDB存儲引擎最早由Innobase Oy公司開發(屬第三方存儲引擎)。從MySQL 5.5版本開始作為表的預設存儲引擎。該存儲引擎是第一個完整支持ACID事務的MySQL存儲引擎,特點是行鎖設計、支持MVCC、支持外鍵、提供一致性非鎖定讀,非常適合OLTP場景的應用使用。目前也是應用最廣泛的存儲引擎。
InnoDB存儲引擎架構包含記憶體結構和磁碟結構兩大部分,總體架構圖如下:
8.0版本:
5.5版本:
2 InnoDB 存儲結構
2.1 磁碟結構
2.1.1 表空間 Tablespaces
InnoDB存儲引擎的邏輯存儲結構是將所有的數據都被邏輯地放在了一個空間中,這個空間中的文件就是實際存在的物理文件(.ibd文件),即表空間。預設情況下,一個資料庫表占用一個表空間,表空間可以看做是InnoDB存儲引擎邏輯結構的最高層,所以的數據都存放在表空間中,例如:表對應的數據、索引、insert buffer bitmap undo信息、insert buffer 索引頁、double write buffer files 等都是放在共用表空間中的。
表空間分為系統表空間(ibdata1文件)(共用表空間)、臨時表空間、常規表空間、Undo表空間和file-per-table表空間(獨立表空間)。系統表空間又包括雙寫緩衝區(Doublewrite buffer)、Change Buffer等
1.系統表空間 System Tablespace
系統表空間可以對應文件系統上一個或多個實際的文件,預設情況下, InnoDB會在數據目錄下創建一個名為.ibdata1,大小為 12M的文件,這個文件就是對應的系統表空間在文件系統上的表示。這個文件是可以自擴展的,當不夠用的時候它會自己增加文件大小。需要註意的一點是,在一個MySQL伺服器中,系統表空間只有一份。從MySQL5.5.7到MySQL5.6.6之間的各個版本中,我們表中的數據都會被預設存儲到這個系統表空間。
show variables like '%innodb_data_file_path%'
2.獨立表空間
在MySQL5.6.6以及之後的版本中, InnoDB並不會預設的把各個表的數據存儲到系統表空間中,而是為每一個表建立一個獨立表空間,也就是說我們創建了多少個表,就有多少個獨立表空間。使用獨立表空間來存儲表數據的話,會在該表所屬資料庫對應的子目錄下創建一個表示該獨立表空間的文件,文件名和表名相同,只不過添加了一個.ibd的擴展名而已。
show variables like '%innodb_file_per_table%'
獨立表空間只是存放數據、索引和插入緩衝Bitmap頁,其他類的數據如回滾(undo)信息、插入緩衝索引頁、系統事務信息、二次寫緩衝等還是存放在原來的系統表空間。
3.其他類型的表空間
隨著MySQL的發展,除了上述兩種表空間之外,現在還新提出了一些不同類型的表空間,比如通用表空間 (general tablespace)、undo表空間(undo tablespace)、臨時表空間(temporary tablespace)等
4.表空間結構
表空間又由段(segment)、區( extent)、頁(page)組成,頁是InnoDB磁碟管理的最小單位。在我們執行sql時,不論是查詢還是修改,mysql 總會把數據從磁碟讀取內記憶體中,而且在讀取數據時,不會單獨加在一條數據,而是直接載入數據所在的數據頁到記憶體中。表空間本質上就是一個存放各種頁的頁面池。
「頁」是InnoDB管理存儲空間的基本單位,也是記憶體和磁碟交互的基本單位。也就是說,哪怕你需要1位元組的數據,InnoDB也會讀取整個頁的數據,InnoDB有很多類型的頁,它們的用處也各不相同。比如:有存放undo日誌的頁、有存放INODE信息的頁、有存放Change Buffer信息的頁、存放用戶記錄數據的頁(索引頁)等等。
InnoDB預設的頁大小是16KB,在初始化表空間之前可以在配置文件中進行配置,一旦資料庫初始化完成就不可再變更了。
SHOW VARIABLES LIKE 'innodb_page_size'
2.1.2 重寫日誌 redo log文件
redo log記錄資料庫的變更,資料庫崩潰後,會從redo log獲取事務信息,進行系統恢復。redo log在磁碟上表現為ib_logfile0和ib_logfile1兩個文件。MySQL會在事務的提交前將redo日誌刷新回磁碟。
在同一時間提交的事務,會採用組提交(group commit)的方式一次性刷新回磁碟。從而避免一個事務刷新一次磁碟,提高性能。
2.1.3 Double Write Files 雙寫緩衝文件
double write 是保障 InnoDB 存儲引擎操作數據頁的可靠性。double write 分為兩部分組成,一部分在記憶體中的 double write buffer, 大小為 2MB,另一部分是物理磁碟上共用表空間中連續的128個數據頁,即2個區大小(同樣是2MB)。
2.2 記憶體結構
InnoDB存儲引擎是基於磁碟存儲的,並將其中的記錄按照頁的方式進行管理,因此可將其視為基於磁碟的資料庫系統(Disk-base Database)。在資料庫中CPU速度與磁碟速度是有很大差距的,基於磁碟的資料庫系統通常使用緩衝池技術來提高資料庫的整體性能。結構如圖所示:
2.1.1 緩存池 Buffer Pool
Buffer Pool是InnoDB記憶體中的一塊占比較大的區域,通過記憶體的速度來彌補磁碟速度慢對資料庫性能的影響。在資料庫中進行讀取頁的操作,首先將從磁碟讀到的頁放在緩衝池中,這個過程稱為將頁”FIX”在緩衝池中,下次再讀到相同的頁時,首先判斷該頁是否在緩衝池中,若在緩衝池中,直接讀取該頁,否則讀取磁碟上的頁。
對於資料庫中的頁的修改操作,首先修改在緩衝池中的頁,然後再以一定頻率刷新到磁碟上,這裡需要註意的是,頁從緩衝池刷新回磁碟的操作並不是在每次頁發生更新時觸發,而是通過一種稱為Checkpoint的機制刷新回磁碟。
緩存區緩存的數據頁類型有:索引頁,數據頁,undo頁,插入緩衝(change buffer),自適應哈希索引(adaptive hash index),InnoDB存儲鎖信息(lock info),數據字典信息(data dictionary)。數據頁和索引頁占據了緩衝池很大部分。
InnoDB1.0.x版本開始,允許有多個緩衝池實例,每個頁根據哈希值平均分配到不同緩衝池的實例中,這樣可以減少資料庫內部資源競爭,增加資料庫的併發處理能力。
show variables like 'innodb_buffer_pool_instances'
整個Buffer Pool的說明用一張圖來概括如下:
1.LRU List,Free List和Flush List——管理InnoDB記憶體區域
為了緩存管理的效率,緩衝池被實現為頁鏈表,採用三個鏈表維護記憶體頁,而記憶體頁也因此對應 3 種狀態: Free 尚未使用; Clean 已使用但未修改; Dirty(臟頁)已修改;Free頁只位於Free List,而Clean和Dirty頁同時位於LRU List,Dirty頁只存在於Flush List;
1)LRU List:
資料庫中的緩衝池是通過LRU(Latest Recent Used,最近最少使用)演算法來進行管理的。即最頻繁使用的頁在LRU列表的前端,而最少使用的頁在LRU列表的尾端。當緩衝池不能存放新讀取到的頁時,將首先釋放LRU列表中尾端的頁。
在InnoDB存儲引擎中,緩衝池中頁的大小預設為16KB,同樣使用LRU演算法對緩衝池進行管理。稍有不同的是InnoDB存儲引擎對傳統的LRU演算法做了一些優化。在InnoDB的存儲引擎中,LRU列表中還加入了midpoint位置。新讀取到的頁,雖然是最新訪問的頁,但並不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置。這個演算法在InnoDB存儲引擎下稱為midpoint insertion strategy。在預設配置下,該位置在LRU列表長度的5/8處。
SHOW VARIABLES LIKE'innodb_old_blocks_pct'
參數innodb_old_blocks_pct預設值為37,表示新讀取的頁插入到LRU列表尾端的37%的位置(差不多3/8的位置)。在InnoDB存儲引擎中,把midpoint之後的列表稱為old列表,之前的列表稱為new列表。可以簡單地理解為new列表中的頁都是最為活躍的熱點數據
- 那為什麼不採用朴素的LRU演算法,直接將讀取的頁放入到LRU列表的首部呢?
這是因為若直接將讀取到的頁放入到LRU的首部,那麼某些SQL操作可能會使緩衝池中的頁被刷新出,從而影響緩衝池的效率。常見的這類操作為索引或數據的掃描操作。這類操作需要訪問表中的許多頁,甚至是全部的頁,而這些頁通常來說又僅在這次查詢操作中需要,並不是活躍的熱點數據。如果頁被放入LRU列表的首部,那麼非常可能將所需要的熱點數據頁從LRU列表中移除,而在下一次需要讀取該頁時,InnoDB存儲引擎需要再次訪問磁碟。
- 解決熱點數據被移除LRU列表
InnoDB存儲引擎引入了另一個參數來進一步管理LRU列表,這個參數是innodb_old_blocks_time,用於表示頁讀取到mid位置後需要等待多久才會被加入到LRU列表的熱端,通過這個方法儘可能使LRU列表中熱點數據不被刷出。
SHOW VARIABLES LIKE'innodb_old_blocks_time'
當有新的數據從磁碟查詢到記憶體時,會寫入到 old sub list 的頭部,當此數據再次被查詢的時候,即在 old sublist 中命中之後,才會放入 new sublist 的頭部。當頁從LRU列表的old部分加入到new部分時,稱此時發生的操作為page made young;如果因為innodb_old_blocks_time的設置導致頁沒有從old部分移動到new部分的操作,稱為page not made young。
通過命令SHOW ENGINE INNODB STATUS可以觀察到如下內容:
SHOW ENGINE INNODB STATUS
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 10620037
Buffer pool size 8191 // 表示當前緩衝池中記憶體頁的數量,記憶體池的大小=Buffer pool size*16KB
Free buffers 1025 //表示當前FREE列表中頁的數量;
Database pages 6985 //LRU列表中頁的數量;
Old database pages 2558 //
Modified db pages 0 //顯示了臟頁的數量;
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 4656751, not young 61021911 //表示是否發生了頁在LRU隊列上的移動;
0.00 youngs/s, 0.00 non-youngs/s //表示每秒兩類操作發生的次數;
Pages read 1036977, created 686192, written 21243071
0.00 reads/s, 0.00 creates/s, 0.28 writes/s
//表示緩衝池的命中率,正常情況下命中率如果低於95%,則需要觀察是否因為全表掃描引起了LRU隊列被污染的問題
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 6985, unzip_LRU len: 0
I/O sum[17]:cur[0], unzip sum[0]:cur[0]
- 頁壓縮功能
InnoDB存儲引擎從1.0.x版本開始支持壓縮頁的功能,即將原本16KB的頁壓縮為1KB、2KB、4KB和8KB。而由於頁的大小發生了變化,LRU列表也有了些許的改變。對於非16KB的頁,是通過unzip_LRU列表進行管理的,LRU中的頁包含了unzip_LRU列表中的頁。
對於壓縮頁的表,每個表的壓縮比率可能各不相同。可能存在有的表頁大小為8KB,有的表頁大小為2KB的情況。unzip_LRU是怎樣從緩衝池中分配記憶體的呢?
首先,在unzip_LRU列表中對不同壓縮頁大小的頁進行分別管理。其次,通過伙伴演算法進行記憶體的分配。例如對需要從緩衝池中申請頁為4KB的大小,其過程如下:
- 檢查4KB的unzip_LRU列表,檢查是否有可用的空閑頁;
- 若有,則直接使用;
- 否則,檢查8KB的unzip_LRU列表;
- 若能夠得到空閑頁,將頁分成2個4KB頁,存放到4KB的unzip_LRU列表;
- 若不能得到空閑頁,從LRU列表中申請一個16KB的頁,將頁分為1個8KB的頁、2個4KB的頁,分別存放到對應的unzip_LRU列表中。
2)Free List:
free list 定義是當前沒有被使用的記憶體頁,也就是空閑的記憶體頁,當執行查詢操作時,如果頁已經在 buffer pool 中了,則查詢到直接返回,如果沒有在 buffer pool,並且 free list 不為空,則會從磁碟中查詢對應的數據,放入 free list 的某一頁中,並且把這頁從 free list 中移除,放入 LRU 隊列中。Flush List中的臟頁在執行了刷盤操作後會將空間還給Free List,通過這種方式可以解決空間碎片化
LRU列表用來管理已經讀取的頁,但當資料庫剛啟動時,LRU列表是空的,即沒有任何的頁。這時頁都存放在Free列表中。當需要從緩衝池中分頁時,首先從Free列表中查找是否有可用的空閑頁,若有則將該頁從Free列表中刪除,放入到LRU列表中。否則,根據LRU演算法,淘汰LRU列表末尾的頁,將該記憶體空間分配給新的頁。
從上面可以看出 【SHOW ENGINE INNODB STATUS】 :
- Free buffers表示當前Free列表中頁的數量,Database pages表示LRU列表中頁的數量。可能的情況是Free buffers與Database pages的數量之和不等於Buffer pool size。因為緩衝池中的頁還可能會被分配給自適應哈希索引、Lock信息、Change Buffer等頁,而這部分頁不需要LRU演算法進行維護,因此不存在於LRU列表中。
- pages made young顯示了LRU列表中頁移動到前端的次數,youngs/s、non-youngs/s表示每秒這兩類操作的次數。
- 這裡還有一個重要的觀察變數——Buffer pool hit rate,表示緩衝池的命中率,通常該值不應該小於95%。若發生Buffer pool hit rate的值小於95%這種情況,用戶需要觀察是否是由於全表掃描引起的LRU列表被污染的問題。
3)Flush List:
在LRU列表中的頁被修改後,稱該頁為臟頁(dirty page),即緩衝池中的頁和磁碟上的頁的數據產生了不一致。這時資料庫會通過CHECKPOINT機制將臟頁刷新回磁碟,而Flush列表中的頁即為臟頁列表。需要註意的是,臟頁既存在於LRU列表中,也存在於Flush列表中。LRU列表用來管理緩衝池中頁的可用性,Flush列表用來管理將頁刷新回磁碟,二者互不影響。
Flush List中的臟頁在執行了刷盤操作後會將空間還給Free List。
同LRU列表一樣,Flush列表也可以通過命令SHOW ENGINE INNODB STATUS來查看,前面例子中Modified db pages 就顯示了臟頁的數量。
2.Checkpoint技術
資料庫在發生增刪查改操作的時候,都是先在buffer pool中完成的,為了提高事物操作的效率,buffer pool中修改之後的數據,並沒有立即寫入到磁碟,這有可能會導致記憶體中數據與磁碟中的數據產生不一致的情況。
倘若每次一個頁的變化,就將新頁的版本刷新到磁碟,那麼這個開銷是非常大的,若熱點數據集中在某幾個頁中,那麼資料庫的性能就會變得非常差。同時,如果在從緩衝池將頁的的新版本刷新到磁碟時發生了宕機,那麼數據就不能恢復了,為了避免這種情況,當前事務資料庫系統普遍都採用了Write Ahead Log策略,即當事務提交時,先寫重做日誌,再修改頁,當由於發生宕機而導致數據丟失時,可以通過重做日誌來完成數據的恢復。這也是事務ACID中D(Durability持久性)的要求。
checkpoint的作用:
- 縮短資料庫的恢復時間
- 緩衝池不夠用時,將臟頁刷新到磁碟
- 重做日誌不可用時,刷新臟頁
checkpoint的分類 - sharp checkpoint:在關閉資料庫的時候,將buffer pool中的臟頁全部刷新到磁碟中。
- fuzzy checkpoint:資料庫正常運行時,在不同的時機,將部分臟頁寫入磁碟,進刷新部分臟頁到磁碟,也是為了避免一次刷新全部的臟頁造成的性能問題。
2.2.2 寫緩衝 Change Buffer
在MySQL5.5之前,叫插入緩衝(Insert Buffer),只針對INSERT做了優化;現在對DELETE和UPDATE也有效,叫做寫緩衝(Change Buffer)。它是一種應用在非唯一普通索引頁(non-unique secondary index page)不在緩衝池中,對頁進行了寫操作,並不會立刻將磁碟頁載入到緩衝池,而僅僅記錄緩衝變更(Buffer Changes),等未來數據被讀取時,再將數據合併(Merge)恢復到緩衝池中的技術。寫緩衝的目的是降低寫操作的磁碟IO,提升資料庫性能。
數據的修改分為兩個情況:
1.當修改的數據頁在緩衝池時
上文講過,通過LRU、Flush List的管理,資料庫不是直接寫入磁碟中,是先將redo log寫入到磁碟,再通過checkpoint機制,將這些“臟數據頁”同步地寫入磁碟,等於是將這期間發生的n次的落盤合併成了一次落盤。因為有redo log是落盤的,所以即使資料庫崩潰,緩存中的數據頁全部丟失,也可以通過redo log將這些數據頁找回來。
redo log是資料庫用來在崩潰的時候進行數據恢復的日誌,redo log的寫入策略可以通過參數控制,並不一定是每一次寫操作之後立即落盤redo log,在部分參數下,redo log可能是每秒集中寫入一次,也有可能採取其他落盤策略,但是無論採用什麼方式,redo log的量都是不會減少的,與數據寫入的覆蓋性不同,後一條redo log是不會覆蓋前一條的,而是增量形式的,因此寫redo log的操作,等同於是對磁碟某一小塊區域的順序I/O,而不像數據落盤一樣的隨機IO在磁碟里寫入,需要磁碟在多個地方移動磁頭。所以redo log的落盤是IO操作當中消耗較少的一種,比數據直接刷回磁碟要優很多。
2.當修改的數據頁不在緩衝池時,不用寫緩衝至少需要下麵的三步:
- 先把需要的索引頁,從磁碟載入到緩衝池,一次磁碟隨機讀操作;
- 修改緩衝池中的頁,一次記憶體操作;
- 寫入 redo log ,一次磁碟順序寫操作;
在沒有命中緩衝池的時候,至少多產生一次磁碟IO,對於寫多讀少的業務場景,性能損耗是很高的
加入寫緩衝優化後,流程優化為:
- 在寫緩衝中記錄這個操作,一次記憶體操作;
- 寫入redo log,一次磁碟順序寫操作;
其性能與這個索引頁在緩衝池中,相近。
3.如何保證數據的一致性?
- 資料庫異常奔潰,能夠從redo log中恢複數據;
- 寫緩衝不只是一個記憶體結構,它也會被定期刷盤到寫緩衝系統表空間;
- 數據讀取時,有另外的流程,將數據合併到緩衝池;
下一次讀到該索引頁:
- 載入索引頁,緩衝池未命中,這次磁碟IO不可避免;
- 從寫緩衝讀取相關信息;
- 恢復索引頁,放到緩衝池LRU和Flush里;(在真正被讀取時,才會被載入到緩衝池中)
4.為什麼寫緩衝優化,僅適用於非唯一普通索引頁呢?
InnoDB里有聚集索引(Clustered Index))和普通索引(Secondary Index)兩種。如果索引設置了唯一(Unique)屬性,在 進行修改操作 時, InnoDB必須進行唯一性檢查 。也就是說, 索引頁即使不在緩衝池,磁碟上的頁讀取無法避免(否則怎麼校驗是否唯一!?)
此時就應該直接把相應的頁放入緩衝池再進行修改。
5.除了數據頁被訪問,還有哪些場景會觸發刷寫緩衝中的數據呢?
- 有一個後臺線程,會認為資料庫空閑時;
- 資料庫緩衝池不夠用時;
- 資料庫正常關閉時;
- redo log寫滿時;(幾乎不會出現redo log寫滿,此時整個資料庫處於無法寫入的不可用狀態)
6.什麼業務場景,適合開啟InnoDB的寫緩衝機制?
- 資料庫大部分是非唯一索引;
- 業務是寫多讀少,或者不是寫後立刻讀取;
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size'
2.2.3 自適應散列索引 Adaptive Hash Index
自適應哈希索引用於優化對BP數據的查詢。InnoDB存儲引擎會監控對二級索引數據的查找,如果觀察到建立哈希索引可以帶來速度的提升(最近連續被訪問三次的數據),則建立哈希索引,自適應哈希索引通過緩衝池的B+樹構造而來,因此建立的速度很快。InnoDB存儲引擎會自動根據訪問的頻率和模式來為某些頁建立哈希索引。(在高負載系統下AHI容易產生資源的爭用,進而引起一些bug導致系統受影響甚至崩潰,故建議關閉該功能)
2.2.4 重做日誌緩衝區 rodo Log Buffer
重做日誌緩衝區,當在MySQL中對InnoDB表進行數據更改時,這些更改首先存儲在InnoDB日誌緩衝區的記憶體中,然後再寫入重做日誌(redo logs)的InnoDB日誌磁碟文件中。他讓MySQL在崩潰的時候具有了恢複數據的能力,即在資料庫發生意外的時候,可以進行數據恢復;
日誌緩衝區log buffer是記憶體存儲區域,用於保存要寫入磁碟上的日誌文件的數據。日誌緩衝區大小由innodb_log_buffer_size 變數定義,預設大小為16MB。
日誌緩衝區的內容定期刷新到磁碟。較大的日誌緩衝區可以運行大型事務,而無需在事務提交之前將重做日誌數據寫入磁碟。因此,如果有更新,插入或刪除許多行的事務,則增加日誌緩衝區的大小可以節省磁碟I/O。
這裡還涉及到一個參數 innodb_flush_log_at_trx_commit :控制如何將日誌緩衝區的內容寫入並刷新到磁碟,預設為1,不建議修改
- 參數為0時,表示事務commit不立即把 redo log buffer 里的數據刷入磁碟文件的,而是依靠 InnoDB 的主線程每秒(此時間由參數innodb_flush_log_at_timeout控制,預設1s)執行一次刷新到磁碟。此時可能你提交事務了,結果 mysql 宕機了,然後此時記憶體里的數據全部丟失。
- 參數為1時,表示事務commit後立即把 redo log buffer 里的數據寫入到os buffer中,並立即執行fsync()操作
- 參數為2時,表示事務commit後立即把 redo log buffer 里的數據寫入到os buffer中,但不立即fsync()SQL執行過程
什麼是binlog
binlog是一個二進位格式的文件,用於記錄用戶對資料庫更新的SQL語句信息,預設情況下,binlog是二進位格式的,不能使用文本工具的命令進行查看,而是使用mysqlbinlog解析查看。
binlog的功能
當數據寫入到資料庫的時候,會同時把更新的SQL語句寫入到相應的binlog文件裡面,同時在使用mysqldump進行備份的時候,只是對一段時間的數據進行了全局備份,但是如果備份後發現資料庫伺服器產生故障,這個時候就要用到binlog日誌了。
binlog和redolog的區別:
- redo log是在InnoDB存儲引擎層產生,而binlog是mysql資料庫的上層產生,而且binlog是二進位格式的日誌,不僅僅針對InnoDB存儲引擎。
- 兩種日誌記錄的內容形式不同,MySQL的binlog是邏輯日誌,而InnoDB存儲引擎層面的重做日誌是物理日誌。
- 兩種日誌與記錄寫入磁碟的時間點不同,二進位日誌只在事物提交完成後進行一次寫入,而redo log的重做日誌在事物的進行過程中不斷地被寫入。
- binlog不是迴圈使用,在寫滿或者重啟之後,會生成新的binlog文件,但是redo log是迴圈使用的。
3 InnoDB 存儲特性
- 寫緩衝 Change Buffer
- 兩次寫 Double Write
InnoDB在把Dirty 臟頁寫回到表空間之前,在記憶體中會線拷貝到連續的記憶體空間double write buffer緩衝區,然後再把它們寫到一個叫doublewrite buffer file的連續磁碟存儲區域內,在寫doublewrite buffer file完成後,InnoDB才會把Dirty pages寫到data file的適當的位置。如果在寫page的過程中發生意外崩潰,InnoDB在稍後的恢復過程中在doublewrite buffer file中找到完好的page副本用於恢復。
為什麼需要雙寫?
InnoDB 的Page Size一般是16KB,其數據校驗也是針對這16KB來計算的,將數據寫入到磁碟是以Page為單位進行操作的。而電腦硬體和操作系統,寫文件是以4KB(512位元組)作為單位的,不能保證MySQL數據頁面16KB的一次性原子寫。試想,在某個Dirty Page flush的過程中,發生了系統斷電(或者OS崩潰),16K的數據只有部分被寫到磁碟上,只有一部分寫是成功的,這種現象被稱為partial page writes。在出現磁碟崩潰的時候,InnoDB 引擎會從共用表空間中的doublewrite找到該頁的一個副本,將其複製到表空間文件,再應用重做日誌,保障 InnoDB 存儲引擎操作數據頁的可靠性。
為什麼不能使用redo log 解決partial page writes?
一旦partial page writes發生,那麼在InnoDB恢復時就很尷尬:redo log的頁大小一般設計為512個位元組,因此redo log page本身不會發生break page。用redo log來解決partial write 理論上是可行的,不過innodb的redo log是物理邏輯日誌,並不是純物理日誌,因此發生partial write後崩潰恢復過程中不能直接應用redo log ,innodb發現break page後實際上會報錯。物理邏輯日誌不是完全冪等的,這取決於重做日誌類型,對於INSERT產生的日誌其不是冪等的。
**
兩次寫的工作流程**
double write由兩部分組成,一部分是InnoDB記憶體中的double write buffer,大小為2MB,另一部分是物理磁碟上的ibdata,系統表空間中大小為2MB,共128個連續的Page(2*1024/16KB=128),即兩個分區(extend)一個段(segment)。其中120個頁用於批量刷新臟頁(如LRU LIST刷新與FLUSH LIST刷新這兩種刷新策略),另外8個頁用於單頁刷新(Single Page Flush)。做區分的原因是批量刷臟是後臺線程做的,不影響前臺線程。而單頁刷新是用戶線程發起的,需要儘快的刷臟頁並替換出一個空閑頁出來。
InnoDB刷新(寫出)緩衝區中的數據頁時採用的是一次寫多個頁的方式:
- 多個頁就可以先順序寫入到double write buffer,並調用fsync()保證這些數據被刷新到double write磁碟(ibdata)。
- 然後數據頁調用fsync()被刷新到實際存儲位置;
- 故障恢復時InnoDB檢查double write Buffer與數據頁原存儲位置的內容,若double write頁處於頁斷裂狀態,則簡單的丟棄;若數據頁不一致,則從double write頁還原。
由於double write頁落盤與數據頁落盤在不同的時間點,不會出現double write頁和數據頁同時發生斷裂的情況,因此doublewrite技術可以解決頁斷裂問題,進而保證了重做日誌能順利進行,資料庫能恢復到一致的狀態。
3.自適應哈希索引 Adaptive Hash Index
4 參考資料
掘金小冊《MySQL 是怎樣運行的:從根兒上理解 MySQL》學習筆記https://www.jianshu.com/p/3394321c11bf
作者:京東物流 鄧鈞蔚
來源:京東雲開發者社區 自猿其說Tech