一篇文章讓你搞懂Mysql InnoDB內存結構
前言
我們都熟悉mysql數據庫服務架構,也清楚SQL的執行順序,Mysql的數據在磁盤和內存中的存儲結構是采用B+樹的數據結構,但是在InnoDB引擎中,數據在內存和磁盤中的展示形式以及怎么和Mysql的服務架構建立聯系,SQL查詢和InnoDB引擎之前的聯系,可能就不是不清楚了。
Mysql的邏輯架構圖如下所示:
InnoDB存儲引擎結構
InnoDB存儲引擎的邏輯存儲結構是什么呢,其實所有的數據都被邏輯地放在了一個空間中這個空間中的文件就是實際存在的物理文件,即表空間。默認情況下,一個數據庫表占用一個表空間,表空間中存放該表對應的數據、索引、insert buffer bitmap undo信息、insert buffer 索引頁、double write buffer 等是放在共享表空間中的。
# 默認一個數據庫表單獨占有一個表空間 show variables like '%innodb_file_per_table%' innodb_file_per_table=ON # 修改設置 SET GLOBAL innodb_file_per_table=OFF; 1.2.3.4.5.
2.1、InnoDB表存儲引擎文件
每個表空間由 段 segment 區 extent 頁 page 組成。頁是數據存儲數據的基本單位,默認大小為 16kb。 區是由連續頁組成的空間,默認大小為 1MB。多個區構成表的段。 InnoDB 邏輯存儲結構
,則直接加載 extent 中剩余的數據頁。
2.3、InnoDB 特性
2.3.1、插入緩存
插入緩沖(Insert Buffer/Change Buffer)為了提升插入性能,insert buffer 是 insert buffer 的增強版,insert buffer 只對插入有效,而change buffer對 insert/update/delete 都有效。插入緩存只對非唯一索引和輔助索引有效,對每一次的插入不是寫到索引頁中,而是先判斷插入的非聚集索引頁是否在緩存中,如果在則直接插入,不存在則插入到 insert buffer 中,按照一定的頻率進行合并操作,寫回到磁盤。這樣將多個插入操作合并進一個操作中,目的是為了減少隨機IO帶來的性能損耗。
2.3.2、二次寫 (double write)
插入緩存給 InnoDB 存儲引擎帶來了性能上的提升,而 double write 則是保障 InnoDB 存儲引擎操作數據頁的可靠性。double write 分為兩部分組成,一部分在內存中的 double write buffer, 大小為 2MB,另一部分是物理磁盤上共享表空間中連續的128個數據頁,即2個區大小(同樣是2MB)。在對緩沖池的臟頁進行刷新時,并不是直接寫磁盤,而是通過 memcpy 函數將臟頁復制到內存中的 doublewrite buffer,之后通過doublewrite buffer 在分兩次,每次1MB 順序地寫入共享表空間的物理磁盤上,然后馬上調用 fsync 將數據同步至磁盤。由于doublewrite 是連續的空間,這樣的順序寫IO開銷不大。在doublewrite頁寫完后,再次離散寫入各個表空間。如果操作系統在將數據頁寫入磁盤發生崩潰,那么在恢復的過程中,InnoDB 引擎會從共享表空間中的doublewrite找到該頁的一個副本,將其復制到表空間文件,再應用重做日志。
。AHI是通過緩沖池中的B+樹頁構造而來,建立速度比較快,而且不需要對整張表建立哈希索引,只是建立熱點頁的索引。AHI默認是開啟的狀態。
2.3.4、異步IO
為了提高磁盤的操作性能, 當前的數據庫系統一般采用異步IO(Asynchronous IO,AIO)的方式來處理磁盤操作,InnoDB 存儲引擎也是如此,AIO的優勢在于減少SQL查詢需要的時間,另外也可以進行IO Merge 操作,就是將多個IO合并為1個IO,這樣就可以提高IOPS的性能。
# 開啟本地 AIO show valiables like 'innodb_use_native_aio';1.2.
2.3.5、刷新鄰接頁
InnoDB 存儲引擎提供了 Flush Neighbor Page(刷新鄰接頁)的特性,當刷新一個臟頁時,InnoDB 存儲引擎會檢測該區內是否存在其它臟頁,如果存在,則一并進行刷新,這樣做得好處顯而易見,可以將多個操作合并成一個操作,對于機械硬盤有著明顯的優勢,但對于固定硬盤,本事就有較高的IOPS,是否開啟需要根據情況而定,參數設置如下:
show varables like 'innodb_flush_neighbors'1.
3、SQL 執行的邏輯
3.1、SQL 執行
mysql寫文件有2塊緩存。一塊是自己定義在內存的log buffer, 另一個是磁盤映射到內存的os cache。 mysql可以 調用 flush 主動將log buffer 刷新到磁盤內存映射,也可以調用 fsync 強制操作系同步磁盤映射文件到磁盤。默認情況下innodb_flush_log_at_trx_commit和sync_binlog 配置都為1。
不僅InnoDB引擎中有 buffer 的概念,這個是在用戶空間中,而且在內核空間中也有 OS buffer的概念
還可以同時調用 flush + fsync, 將緩存直接落盤。 innodb_flush_log_at_trx_commit = 0 就是每秒調用 flush + fsync ,定時器自己維護。 innodb_flush_log_at_trx_commit = 1 就是實時調用 flush + fsync 沒法批處理,性能很低。 innodb_flush_log_at_trx_commit = 2 就是實時flush ,定時 fsync 交給OS維護定時器。 sync_binlog 配置 等于0:表示每次提交事務只write不fsync 等于1:表示每次提交事務都執行fsync 等于n:表示事務在write后,會累積N個事務后才fsync。 show variables like 'sync_binlog'; show variables like 'innodb_flush_log_at_trx_commit'; # 查看 mysql 正在執行的進程 show processlist1.2.3.4.5.6.7.8.9.10.11.12.13.
InnoDB引擎BufferPool、LogBuffer、OS Buffer、Log files之間的關系。
Mysql在執行增刪改sql時,InnoDB引擎的執行步驟如下:
1 執行器拿到需要執行的sql,需要根據更新條件從磁盤中加載需要修改的數據到內存中,也就是存放在buffer pool中。
2 在修改對應的數據之前,需要將其數據進行備份,也就是將數據放進undo log中,方便在事務回滾時進行操作。
3 直接在內存中按照sql語句修改對應的值。
4 修改完后將按照修改后的數據寫redo log buffer。
5 將 redo log的內容進行寫盤操作,這一步的操作參見innodb_flush_at_trx_commit的配置,一般是先寫入系統的緩存中,然后由操作系統DMA異步操作寫入系統文件中。 flush 操作只是把系統內存中的數據寫入操作系統的緩沖中,數據讀寫一般是由內核線程完成的,這一步是數據從用戶線程轉變成內核線程進行操作,在讀寫文件時,在磁盤文件和內存之間會有多級緩存,用于提高數據交換效率,這里的os cache起到的就是這個作用。
6 在寫完redo log 后,然后進行bin log寫入操作。
7 和 redo log的操作類似,也是先寫入os cache再有操作系統刷到磁盤文件中。sync_log的配置如圖所示。一般情況下,數據庫innodb_flush_at_trx_commit 和 sync_log 配置都為1。
8 在redo log 和 bin log寫完后,就可以進行事務提交。在數據進行寫盤操作時, InnoDB采用兩次寫的方式進行寫數據。
先寫redo log再寫bin log的原因: 由于mysql 是通過bin log進行復制傳輸的,如果先提交了redo log,還沒有寫bin log時出現了宕機,mysql 實例恢復時根據 redo log進行恢復,就會造成 從庫和主庫之間的數據不一致。
二進制日志文件的記錄格式為 statement、row 和 mixed,statement 模式就是直接執行sql,如果其中有函數操作(比如數據庫時間設置為 now() )那就會造成數據不準確。row 模式就是同步所有行的數據,如果全表操作修改狀態,那這種模式就不合適了,因此在數據同步時需要根據情況采用 mixed 的混合模式。
3.2、FreeList、LRU List 和 Flush List
Free List空閑列表
記錄所有未被占用的數據頁,按照順序將加載到內存的數據放入buffer pool 中,并刪除對應 Free List 中的節點
LRU List LRU 數據訪問列表
將冷熱數據塊連接起來,根據 LRU 算法進行維護。如果加載進內存的數據一次性放入列表頭部,再不確定這批數據的熱度情況下,會造成一部分數據的淘汰,mysql InnoDB 的做法是將數據放置在靠后的位置,如果數據在1s內被訪問了,才能進入鏈表頭部,即數據熱區。
# 將新加載的數據放置在鏈表的位置 默認為 37 即5/8處, show variables like 'innodb_old_blocks_pct'; # 冷區數據間隔多久訪問才放入鏈表的熱端,默認為1000ms show variables like 'innodb_old_blocks_time';1.2.3.4.
Flush List 刷新臟頁列表
記錄內存中修改的數據頁,使用雙向鏈表進行連接,在方便的時候做落盤操作。
InnoDB 中的 redo log 大小是固定的,是保證事務持久性的,其文件個數也是可以根據需要進行配置,通過循環寫文件的方式來實現的,當 write pos 追趕上 checkpoint 后,這個時候就不能再繼續執行新的命令,需要把check point 往前推進,也就是把redo log 里的內容持久化,騰出空間繼續寫日志。
數據操作
redo log buffer 循環寫入
這里先寫日志再寫磁盤的關鍵點也是一個技術,Write-Ahead Logging(WAL技術)。
關于 redo log 的配置可以參見如下命令執行查看。
登錄后復制
show variables like '%innodb_log%' ------- 執行結果 ------ innodb_log_buffer_size 16777216 innodb_log_checksums ON innodb_log_compressed_pages ON innodb_log_file_size 50331648 innodb_log_files_in_group 2 innodb_log_group_home_dir ./ innodb_log_write_ahead_size 8192 innodb_log_buffer_size 為內存中 redo log buffer 的大小,16777216/1024/1024=16MB innodb_log_file_size 為每個redo log 的大小,50331648/1024/1024=48MB innodb_log_files_in_group 為 redo log 文件組中文件的個數,默認為2個 查看數據庫表狀態 show table status like 'my_table';
最后
整理了一張Mysql知識圖譜分享給大家:
全套的Java面試寶典手冊:性能調優+微服務架構+并發編程+開源框架+分布式”等七大面試專欄,包含Tomcat、JVM、MySQL、SpringCloud、SpringBoot、Dubbo、并發、Spring、SpringMVC、MyBatis、Zookeeper、Ngnix、Kafka、MQ、Redis、MongoDB、memcached等等。
有需要的朋友可以關注公眾號【程序媛小琬】即可獲取。
MySQL SQL
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。