【转】2.2【MySQL】运行原理(二):InnoDB 内存结构、磁盘结构及update sql执行过程分析
前一篇講完了查詢流程,我們是不是再講講更新流程、插入流程和刪除流程?在數據庫里面,我們說的update操作其實包括了更新、插入和刪除。如果大家有看過MyBatis的源碼,應該知道Executor里面也只有doQuery()和doUpdate()的方法,沒有doDelete()和doInsert()。
更新流程和查詢流程有什么不同呢?基本流程也是一致的,也就是說,它也要經過解析器、優化器的處理,最后交給執行器。區別就在于拿到符合條件的數據之后的操作。
緩沖池 Buffer Pool
首先,InnnoDB的數據都是放在磁盤上的,InnoDB操作數據有一個最小的邏輯單位,叫做頁(索引頁和數據頁)。我們對于數據的操作,不是每次都直接操作磁盤,因為磁盤的速度太慢了。 InnoDB使用了一種緩沖池的技術,也就是把磁盤讀到的頁放到一塊內存區域里面。這個內存區域就叫Buffer Pool。
下一次讀取相同的頁,先判斷是不是在緩沖池里面,如果是,就直接讀取,不用再次訪問磁盤。
修改數據的時候,先修改緩沖池里面的頁。內存的數據頁和磁盤數據不一致的時候,我們把它叫做臟頁。InnoDB里面有專門的后臺線程把Buffer Pool的數據寫入到磁盤,每隔一段時間就一次性地把多個修改寫入磁盤,這個動作就叫做刷臟。
BufferPool是InnoDB里面非常重要的一個結構,它的內部又分成幾塊區域。這里我們趁機到官網來認識一下InnoDB的內存結構和磁盤結構。
1.Innodb 內存結構
Innodb的內存結構主要分為 3 個部分: Buffer Pool、Change Buffer、Adaptive HashIndex,另外還有一個(redo)log buffer。
1.1 Buffer Pool
Buffer Pool緩存的是頁面信息,包括數據頁、索引頁。
SHOW STATUS LIKE '%innodb_buffer_pool%'; -- 查看服務器狀態中 Buffer Pool 相關信息 SHOW VARIABLES like '%innodb_buffer_pool%'; -- 查看參數(系統變量)這些狀態都可以在官網查到詳細的含義,用搜索功能。 Buffer Pool默認大小是128M(134217728字節),可以調整。
內存的緩沖池寫滿了怎么辦?(Redis 設置的內存滿了怎么辦?)InnoDB 用 LRU算法來管理緩沖池(鏈表實現,不是傳統的LRU,分成了young和old),經過淘汰的數據就是熱點數據。
內存緩沖區對于提升讀寫性能有很大的作用。思考一個問題:當需要更新一個數據頁時,如果數據頁在BufferPool中存在,那么就直接更新好了。否則的話就需要從磁盤加載到內存,再對內存的數據頁進行操作。也就是說,如果沒有命中緩沖池,至少要產生一次磁盤IO,有沒有優化的方式呢?
1.2 Change Buffer(寫緩沖)
如果這個數據頁不是唯一索引(注:唯一索引就是在同一字段下不能有相同值),也就不需要從磁盤加載索引頁判斷數據是不是重復(唯一性檢查)。這種情況下可以先把修改記錄在內存的緩沖池中,從而提升更新語句(Insert、Delete、Update)的執行速度。這一塊區域就是 Change Buffer。5.5 之前叫Insert Buffer 插入緩沖,現在也能支持delete和update。最后把 Change Buffer 記錄到數據頁的操作叫做 merge。
什么時候發生 merge?有幾種情況:在訪問這個數據頁的時候,或者通過后臺線程、或者數據庫shut down、redo log寫滿時觸發。
如果數據庫大部分索引都是非唯一索引,并且業務是寫多讀少,不會在寫數據后立刻讀取,就可以使用Change Buffer(寫緩沖)。寫多讀少的業務,調大這個值:
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';max_size 代表Change Buffer占Buffer Pool的比例,默認25%。
1.3 Adaptive Hash Index
索引應該是放在磁盤的,為什么要專門把一種哈希的索引放到內存?這里就不說了,把問題的答案放在詳談索引存儲結構推演過程…
1.4 Log Buffer(Redo log)
MySQL 在更新數據時,為了減少磁盤的隨機 IO,因此并不會直接更新磁盤上的數據,而是先更新 Buffer Pool 中緩存頁的數據,等到合適的時間點,再將這個緩存頁持久化到磁盤。而 Buffer Pool 中所有緩存頁都是處于內存當中的,當 MySQL 宕機或者機器斷電,內存中的數據就會丟失,因此 MySQL 為了防止緩存頁中的數據在更新后出現數據丟失的現象,引入了 redo log 機制。
當進行增刪改操作時,MySQL 會在更新 Buffer Pool 中的緩存頁數據時,會記錄一條對應操作的 redo log 日志,這樣如果出現 MySQL 宕機或者斷電時,如果有緩存頁的數據還沒來得及刷入磁盤,那么當 MySQL 重新啟動時,可以根據 redo log 日志文件,進行數據重做,將數據恢復到宕機或者斷電前的狀態,保證了更新的數據不丟失,因此 redo log 又叫做重做日志。它的本質是保證事務提交后,更新的數據不丟失。——用它來實現事務的持久性。
這種日志和磁盤配合的整個過程,其實就是 MySQL 里的 WAL 技術(Write-Ahead Logging),它的關鍵點就是先寫日志,再寫磁盤。
同樣是寫磁盤,為什么不直接寫db file,而是先寫日志?
我們先來了解一下隨機I/O和順序I/O的概念。磁盤的最小組成單元是扇區,通常是512個字節。操作系統和內存打交道,最小的單位是頁Page。操作系統和磁盤打交道,讀寫磁盤,最小的單位是塊Block。
如果我們所需要的數據是隨機分散在不同頁的不同扇區中,那么找到相應的數據需要等到磁臂旋轉到指定的頁,然后盤片尋找到對應的扇區,才能找到我們所需要的一塊數據,一次進行此過程直到找完所有數據,這個就是隨機IO,讀取數據速度較慢。
假設我們已經找到了第一塊數據,并且其他所需的數據就在這一塊數據后邊,那么就不需要重新尋址,可以依次拿到我們所需的數據,這個就叫順序IO。
刷盤是隨機I/O,而記錄日志是順序I/O,順序I/O效率更高。所以,先不斷把內存中bufferpool的數據修改寫入日志,保證不會丟失,然后等到一個適當的時機(系統比較空閑)將操作記錄更新到磁盤里面。達到延遲刷盤時機的目的,進而提升系統吞吐。redo log存在的意義主要就是降低對數據頁刷盤的要求。
redo log(重做日志)有什么特點?
redo log是InnoDB存儲引擎實現的,并不是所有存儲引擎都有。
不是記錄數據頁更新之后的狀態(某一行或某幾行修改成怎樣怎樣),而是記錄當前頁做了什么改動,屬于物理日志。它用來恢復提交后的物理數據頁(恢復數據頁,且只能恢復到最后一次提交的位置)
日志分為物理日志和邏輯日志。
- 物理日志就是直接記錄數據,記錄被修改的page的偏移量,優點就是不依賴原page的內容,用日志的內容可以直接覆蓋到磁盤上面,而缺點就是占用的空間太多,比如新增一個btree索引或者一個update操作。
- 邏輯日志只是記錄關系表上面的元操作,比如update一行數據,delete一行數據等,優點就是比較簡潔而且占用的空間要小,缺點就是需要依賴原page內容,而且會有部分執行和操作一致性的問題。
redo log 分成內存和磁盤兩部分:
- 內存部分:Log Buffer,存在刷盤操作。
- 磁盤部分:對應于 /var/lib/mysql/目錄下的ib_logfile0和ib_logfile1,每個48M。
因為redo log 實際上記錄數據頁的變更,而這種變更記錄是沒必要全部保存,所以 redo log 的大小是固定的,前面的內容會被覆蓋。如下圖所示:
再強調一次,redo log的內容主要是用于崩潰恢復。磁盤的數據文件,數據來自 buffer pool。redo log 寫入磁盤,不是寫入數據文件。
redo log 的內存部分:Log Buffer
當然redo log也不是每一次都直接寫入磁盤,在Buffer Pool里面有一塊內存區域(Log Buffer)專門用來保存即將要寫入日志文件的數據,Innodb_log_buffer默認大小為8M,它一樣可以節省磁盤IO。
那么,Log Buffer什么時候寫入log file,或者說什么日志什么時候刷盤?
在我們寫入數據到磁盤的時候,操作系統本身是有緩存的。flush就是把操作系統緩沖區寫入到磁盤。log buffer寫入磁盤的時機,由一個參數控制,默認是1。
| 0(延遲寫) | logbuffer 將每秒一次地寫入 logfile 中,并且 logfile 的 flush 操作同時進行。 該模式下,在事務提交的時候,不會主動觸發寫入磁盤的操作。 |
| 1(默認,實時寫,實時刷) | 每次事務提交時 MySQL 都會把 logbuffer 的數據寫入 logfile,并且刷到磁盤中去。 |
| 2(實時寫,延遲刷) | 每次事務提交時 MySQL 都會把 logbuffer 的數據寫入 logfile。但是 flush 操作并不會同時進行。該模式下,MySQL 會每秒執行一次 flush 操作。 |
以上就是MySQL的內存結構。總結一下,分為:Buffer pool、change buffer、Adaptive Hash Index、 log buffer。
2.Innodb 磁盤結構(表空間)
磁盤結構里面主要是各種各樣的表空間,叫做Table space。表空間可以看做是 InnoDB 存儲引擎邏輯結構的最高層,所有的數據都存放在表空間中。InnoDB的表空間分為5大類。
2.1 system tablespace(系統表空間)
在默認情況下 InnoDB 存儲引擎有一個共享表空間(對應文件/var/lib/mysql/ibdata1),也叫系統表空間。
InnoDB系統表空間包含InnoDB數據字典和雙寫緩沖區,ChangeBuffer和UndoLogs),如果沒有指定file-per-table,也包含用戶創建的表和索引數據。
- undo在后面介紹,因為有獨立的表空間。
- 數據字典:由內部系統表組成,存儲表和索引的元數據(定義信息)。
- 雙寫緩沖(InnoDB的一大特性):InnoDB的頁和操作系統的頁大小不一致,InnoDB頁大小一般為16K,操作系統頁大小為4K,InnoDB的頁寫入到磁盤時,一個頁需要分4次寫。
如果存儲引擎正在寫入頁的數據到磁盤時發生了宕機,可能出現頁只寫了一部分的情況,比如只寫了4K,就宕機了,這種情況叫做部分寫失效(partial page write),可能會導致數據丟失。
show variables like 'innodb_doublewrite';我們不是有 redo log 嗎?但是有個問題,如果這個頁本身已經損壞了,用它來做崩潰恢復是沒有意義的。所以在對于應用redo log之前,需要一個頁的副本。如果出現了寫入失效,就用頁的副本來還原這個頁,然后再應用redo log。這個頁的副本就是?double write,InnoDB的雙寫技術。通過它實現了數據頁的可靠性。
跟 redo log 一樣,double write 由兩部分組成,一部分是內存的double write,一個部分是磁盤上的double write。因為double write是順序寫入的,不會帶來很大的開銷。在默認情況下,所有的表共享一個系統表空間,這個文件會越來越大,而且它的空間不會收縮。
2.2 file-per-table tablespaces(獨占表空間)
我們可以讓每張表獨占一個表空間。這個開關通過innodb_file_per_table設置,默認開啟。
SHOW VARIABLES LIKE 'innodb_file_per_table';開啟后,則每張表會開辟一個表空間,這個文件就是數據目錄下的 ibd文件(例如
/var/lib/mysql/forum/user_innodb.ibd),存放表的索引和數據。
但是其他類的數據,如回滾(undo)信息,插入緩沖索引頁、系統事務信息,二次寫緩沖(Double write buffer)等還是存放在原來的共享表空間內。
2.3 general tablespaces(通用表空間)
通用表空間也是一種共享的表空間,跟ibdata1類似。可以創建一個通用的表空間,用來存儲不同數據庫的表,數據路徑和文件可以自定義。語法:
create table space ts2673 add datafile '/var/lib/mysql/ts2673.ibd' file_block_size=16K engine=innodb;在創建表的時候可以指定表空間,用ALTER修改表空間可以轉移表空間。
create table t2673(idinteger) tablespace ts2673;不同表空間的數據是可以移動的。刪除表空間需要先刪除里面的所有表:
drop table t2673; drop tablespace ts2673;2.4 temporary tablespaces(臨時表空間)
存儲臨時表的數據,包括用戶創建的臨時表,和磁盤的內部臨時表。對應數據目錄下的ibtmp1文件。當數據服務器正常關閉時,該表空間被刪除,下次重新產生。
2.5 undo log tablespace
undo log(撤銷日志或回滾日志)記錄了事務發生之前的數據狀態(不包括select) 。用來保證在必要時實現回滾,如果另一個事務需要在一致性讀操作中查看原始數據,則從undo日志記錄中檢索未修改的數據,也就是說MVCC機制也依賴于undo log來實現。在執行 undo 的時候,僅僅是將數據從邏輯上恢復至事務之前的狀態,而不是從物理頁面上操作實現的,屬于邏輯格式的日志。
redo Log 和 undo Log 與事務密切相關,統稱為事務日志。但與redo log不同的是:
- redo log 是存儲的是物理日志,undo log存儲的是邏輯日志
- redo log 是重做日志,提供?前滾?操作;undo log 是回退日志,提供?回滾?操作。
undo 是在事務開始之前保存的被修改數據的一個版本,產生undo日志的時候,同樣會伴隨類似于保護事務持久化機制的 redo log 的產生。
- Redo 記錄某?數據塊?被修改?后?的值,可以用來恢復未寫入 data file 的已成功事務更新的數據。-- 保證事務持久性
- Undo 記錄某?數據?被修改?前?的值,可以用來在事務失敗時進行 rollback;-- 保證事務原子性
比如某一時刻數據庫 DOWN 機了,有兩個事務,一個事務已經提交,另一個事務正在處理。數據庫重啟的時候就要根據日志進行前滾及回滾,把已提交事務的更改寫到數據文件,未提交事務的更改恢復到事務開始前的狀態。
- 當數據 crash-recovery 時,通過 redo log 將所有已經在存儲引擎內部提交的事務應用 redo log 恢復
- 所有已經 prepared 但是沒有 commit 的 transactions 將會應用 undo log 做 roll back
undo Log 的數據默認在系統表空間ibdata1 文件中,因為共享表空間不會自動收縮,也可以單獨創建一個undo表空間。
==>update過程分析
有了這些日志之后,我們來總結一下一個更新操作的流程,這是一個簡化的過程(name原值是zhangsan)。
update user set name='penyuyan' where id=1; 1. 事務開始,從內存或磁盤取到這條數據,返回給Server 的執行器; 2. 執行器修改這一行數據的值為penyuyan; 3. 記錄 name=zhangsan 到 undo log; 4. 記錄 name=penyuyan 到 redo log; 5. 調用存儲引擎接口,在內存(Buffer Pool)中修改 name=zhangsan; 6. 事務提交另外,內存和磁盤之間,工作著很多后臺線程。后臺線程的主要作用是負責刷新內存池中的數據和把修改的數據頁刷新到磁盤。后臺線程分為:master thread,IO thread,purge thread,page cleaner thread。
- master thread:負責刷新緩存數據到磁盤并協調調度其它后臺進程
- IO thread:分為 insert buffer、 log、 read、 write進程。分別用來處理 insert buffer、重做日志、讀寫請求的IO回調
- purge thread:用來回收undo 頁
- page cleaner thread:用來刷新臟頁
除了 InnoDB 架構中的日志文件,MySQL 的 Server 層也有一個日志文件,叫做binlog,它可以被所有的存儲引擎使用。
binlog 以事件的形式記錄了所有的DDL和DML語句(因為它記錄的是操作而不是數據值,屬于邏輯日志),可以用來做主從復制和數據恢復。跟redo log不一樣,它的文件內容是可以追加的,沒有固定大小限制。
在開啟了binlog功能的情況下,我們可以把binlog導出成SQL語句,把所有的操作重放一遍,來實現數據的恢復。binlog的另一個功能就是用來實現主從復制,它的原理就是從服務器讀取主服務器的binlog,然后執行一遍。
參考鏈接:對比總結三大日志:重做日志(redo log),回滾日志(undo log),二進制日志(binlog)
有了這兩個日志之后,我們來看一下一條更新語句是怎么執行的:
update teacher set name='盆魚宴' where id=1 1. 先查詢到這條數據,如果有緩存,也會用到緩存。 2. 把name改成盆魚宴,然后調用引擎的API接口,寫入這一行數據到內存,同時記錄redo log。這時 redo log 進入prepare 狀態,然后告訴執行器,執行完成了,可以隨時提交。 3. 執行器收到通知后記錄binlog,然后調用存儲引擎接口, 設置redolog為commit狀態。 4. 更新完成。需要特別注意的幾點:
總結腦圖
總結
以上是生活随笔為你收集整理的【转】2.2【MySQL】运行原理(二):InnoDB 内存结构、磁盘结构及update sql执行过程分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 500万内最好的家用旗舰SUV!理想L9
- 下一篇: 【转】MySQL日期函数与日期转换格式化