系列
MySQL實(shí)戰(zhàn)45講閱讀筆記-MySQL入門
MySQL實(shí)戰(zhàn)45講閱讀筆記-日志
MySQL實(shí)戰(zhàn)45講閱讀筆記-鎖
MySQL實(shí)戰(zhàn)45講閱讀筆記-索引
MySQL實(shí)戰(zhàn)45講閱讀筆記-MVCC
MySQL的使用作為后端必須要掌握的一個(gè)技術(shù),還是需要了解下基本的原理,方便在我們工程實(shí)踐中對(duì)數(shù)據(jù)庫(kù)的運(yùn)用有更深入的了解,自己寫(xiě)的SQL背后會(huì)發(fā)生什么事情,所以還是很有必要學(xué)習(xí)下基本的原理的;

當(dāng)然這幾篇是對(duì)MySQL官方文檔、網(wǎng)上博客和MySQL實(shí)戰(zhàn)45講的總結(jié),沒(méi)有翻閱源碼,總結(jié)的知識(shí)點(diǎn)都是來(lái)自于別人的和別人的別人的所以可能相當(dāng)淺顯且錯(cuò)誤百出,終究只是個(gè)閱讀筆記;
模塊

連接器
連接器負(fù)責(zé)跟客戶端建立連接、獲取權(quán)限、維持和管理連接。
在收到連接請(qǐng)求后,連接器會(huì)到權(quán)限表里面查出你擁有的權(quán)限。之后這個(gè)連接里面的權(quán)限判斷邏輯,都將依賴于此時(shí)讀到的權(quán)限。
可以通過(guò)show processlist或者show full processlist顯示當(dāng)前連接,前者只顯示100條后者顯示全部,也可以到information_schema的PROCESSLIST表查看;
長(zhǎng)連接
在連接在建立之后,就一直打開(kāi),后續(xù)請(qǐng)求可以重用此連接?;静襟E是:連接→數(shù)據(jù)傳輸→維護(hù)連接→數(shù)據(jù)傳輸..→關(guān)閉連接。
優(yōu)勢(shì):建立連接的過(guò)程通常是比較復(fù)雜,使用長(zhǎng)連接可以避免頻繁建立連接的開(kāi)銷;
劣勢(shì):維護(hù)長(zhǎng)連接是需要消耗內(nèi)存的。如果使用過(guò)多長(zhǎng)連接,會(huì)消耗大量?jī)?nèi)存資源,連接太少會(huì)影響用戶的響應(yīng)時(shí)間。短連接
執(zhí)行操作后就關(guān)閉連接?;揪褪敲恳淮尾僮鲾?shù)據(jù)庫(kù),都要打開(kāi)和關(guān)閉數(shù)據(jù)庫(kù)連接,基本步驟是:連接→數(shù)據(jù)傳輸→關(guān)閉連接。
優(yōu)勢(shì):用完即斷,不會(huì)占用過(guò)多內(nèi)存;適用高并發(fā)情況下的請(qǐng)求,保證效率;
劣勢(shì):建立和銷毀連接開(kāi)銷大,在高并發(fā)情況下瞬時(shí)請(qǐng)求過(guò)大容易造成內(nèi)存資源緊張;
不同場(chǎng)景下有不同的選擇,沒(méi)有絕對(duì)正確只有適合項(xiàng)目的,JAVA中配合JDBC的連接池一般情況下是推薦使用長(zhǎng)連接;長(zhǎng)連接的連接時(shí)長(zhǎng)是通過(guò)wait_timeout控制;
線程管理也是在這個(gè)模塊,可以使用show variables like 'thread%'查看相關(guān)參數(shù);
-------------------------
thread_cache_size 28
thread_handling one-thread-per-connection
thread_stack 262144
-------------------------
- thread_handling表示線程配置模型
- One-thread-per-connection
每個(gè)客戶端的連接都對(duì)應(yīng)著一個(gè)線程,相應(yīng)的操作也在同一個(gè)線程。請(qǐng)求結(jié)束后,銷毀線程,這種方式在高并發(fā)情況下,會(huì)導(dǎo)致線程的頻繁創(chuàng)建和釋放; - No-Threads
在主線程上面處理連接 - Pool-of-threads
使用線程池,會(huì)在服務(wù)器上維護(hù)了一個(gè)線程池,避免為每個(gè)連接都創(chuàng)建銷毀一個(gè)線程。
- thread_cache_size
服務(wù)器最大緩存的線程數(shù)以供重用。當(dāng)客戶端斷開(kāi)連接時(shí),如果客戶端緩存的線程少于thread_cache_size,則將其放入緩存中 。當(dāng)客戶端有新的連接時(shí)先去從高速緩存中獲取的線程來(lái)滿足線程請(qǐng)求,并且僅當(dāng)高速緩存為空時(shí)才創(chuàng)建新線程。
默認(rèn)值:8 + (max_connections / 100)
show global status like 'thread%'
-------------------------
Threads_cached 13 當(dāng)前線程池中緩存有多少空閑線程
Threads_connected 37 當(dāng)前的線程使用數(shù)量
Threads_created 196289 已經(jīng)創(chuàng)建的線程總數(shù)
Threads_running 1 當(dāng)前激活的線程數(shù)
-------------------------
可以通過(guò)高峰期查看Threads_created和Threads_created調(diào)整thread_cache_size;
根據(jù)物理內(nèi)存推薦值(網(wǎng)上都這么說(shuō)) 1G->8; 2G->16;3G->32; >3G->64
查詢緩存
Query Cache是用來(lái)緩存所執(zhí)行的SELECT語(yǔ)句以及該語(yǔ)句的結(jié)果集,MySql在實(shí)現(xiàn)Query Cache的具體技術(shù)細(xì)節(jié)上類似典型的KV存儲(chǔ),就是將SELECT語(yǔ)句和該查詢語(yǔ)句的結(jié)果集做了一個(gè)HASH映射并保存在一定的內(nèi)存區(qū)域中;如果緩存命中,將省略優(yōu)化器和執(zhí)行器的相關(guān)步驟,效率極高;
查詢緩存的失效非常頻繁,只要有對(duì)一個(gè)表的更新,這個(gè)表上所有的查詢緩存都會(huì)被清空。從MySQL5.7.20就不被推薦使用,而在MySQL8中已經(jīng)刪除該模塊了;只有在特定的靜態(tài)表中才推薦使用;
show global status like 'Qcache%'
-------------------------
Qcache_free_blocks 1 緩存池中空閑塊的個(gè)數(shù)
Qcache_free_memory 1031832 緩存中空閑內(nèi)存量
Qcache_hits 0 緩存命中次數(shù)
Qcache_inserts 0 緩存寫(xiě)入次數(shù)
Qcache_lowmem_prunes 0 因內(nèi)存不足刪除緩存次數(shù)
Qcache_not_cached 1329826476 查詢未被緩存次數(shù)
Qcache_queries_in_cache 0 當(dāng)前緩存中緩存的SQL數(shù)量
Qcache_total_blocks 1 緩存總block數(shù)
-------------------------
query_cache_type可以設(shè)置為0(緩存禁用)或者2(通過(guò)SQL_CACHE指定需要緩存的查詢);
打開(kāi)查詢緩存對(duì)讀和寫(xiě)操作都會(huì)帶來(lái)額外消耗:讀查詢?cè)陂_(kāi)始之前必須先檢查是否命中緩存;如果這個(gè)讀查詢可以被緩存,那么當(dāng)完成執(zhí)行后,需要將結(jié)果存入緩存;每次寫(xiě)入操作時(shí),需要將對(duì)應(yīng)表的所有緩存都設(shè)置失效。如果緩存較大或碎片很多,則會(huì)帶來(lái)很大消耗。
優(yōu)化器
在創(chuàng)建內(nèi)部分析樹(shù)之后,MySQL應(yīng)用了各種優(yōu)化技術(shù)。這些技術(shù)可以包括,重寫(xiě)查詢,掃描表的順序以及選擇要使用的正確索引。對(duì)于連接查詢,MySQL優(yōu)化器調(diào)查的可能計(jì)劃數(shù)量隨著查詢中引用的表的數(shù)量呈指數(shù)增長(zhǎng)。對(duì)于少量表(通常小于7到10),這不是問(wèn)題。但是,當(dāng)提交較大的查詢時(shí),在查詢優(yōu)化中花費(fèi)的時(shí)間可能很容易成為服務(wù)器性能的主要瓶頸。
更靈活的查詢優(yōu)化方法使用戶能夠控制優(yōu)化器在搜索最佳查詢?cè)u(píng)估計(jì)劃時(shí)的詳盡程度。一般的想法是,優(yōu)化器調(diào)查的計(jì)劃越少,編譯查詢所花費(fèi)的時(shí)間就越少。另一方面,由于優(yōu)化器會(huì)跳過(guò)某些計(jì)劃,因此可能無(wú)法找到最佳計(jì)劃。

InnoDB
InnoDB是一種平衡高可靠性和高性能的通用存儲(chǔ)引擎。在MySQL 5.7中,InnoDB是默認(rèn)的MySQL存儲(chǔ)引擎。
優(yōu)勢(shì)
DML操作遵循ACID(原子性,一致性,隔離性和持久性)模型,具有提交,回滾和崩潰恢復(fù)功能的事務(wù)來(lái)保護(hù)用戶數(shù)據(jù)。
行級(jí)鎖定和Oracle風(fēng)格的一致性讀取可提高多用戶并發(fā)性和性能。
InnoDB表格將數(shù)據(jù)排列在磁盤上,以根據(jù)主鍵優(yōu)化查詢 。每個(gè)InnoDB表都有一個(gè)稱為聚簇索引的主鍵索引 ,用于組織數(shù)據(jù)以最小化主鍵查找的I / O.
InnoDB表的DML語(yǔ)句在事務(wù)的上下文中操作,因此它們的效果可以作為單個(gè)單元提交或回滾。
- DML
數(shù)據(jù)操縱語(yǔ)言,一組 SQL執(zhí)行語(yǔ)句INSERT、UPDATE、DELETE和SELECT ... FOR UPDATE;

InnoDB內(nèi)存結(jié)構(gòu)
緩沖池 Buffer Pool
緩沖池是主存儲(chǔ)器中的一個(gè)區(qū)域,用于在訪問(wèn)時(shí)緩存表和索引數(shù)據(jù);所有的對(duì)數(shù)據(jù)庫(kù)的操作都是先對(duì)緩沖池中的操作,有個(gè)后臺(tái)線程定期修改到磁盤里面;為了提高緩存管理的效率,緩沖池是由多個(gè)數(shù)據(jù)頁(yè)(頁(yè)面可以包括一行或多行數(shù)據(jù),每個(gè)頁(yè)16k默認(rèn)innodb_page_size=16k)構(gòu)成的一個(gè)鏈表,使用LRU(least recently used)算法管理鏈表;
如果對(duì)一個(gè)數(shù)據(jù)頁(yè)進(jìn)行修改后需要讀取其內(nèi)容,則直接在內(nèi)存中讀取,加速了讀取數(shù)據(jù)的效率,通過(guò)show engine innodb status可以直接查看Buffer Pool的命中率Buffer pool hit rate,所謂的命中率就是訪問(wèn)的數(shù)據(jù)頁(yè)在內(nèi)存中不需要到磁盤加載的;
數(shù)據(jù)頁(yè)淘汰算法-LRU

當(dāng)緩沖池中的數(shù)據(jù)頁(yè)全部被使用了,這時(shí)候需要從磁盤加載一個(gè)新的頁(yè)面時(shí)肯定需要淘汰掉某個(gè)頁(yè)面,
innodb buffer pool使用的是最近最少使用(LRU)算法,同時(shí)順便對(duì)這個(gè)算法修改了一下;整個(gè)鏈表分為
Young區(qū)和Old區(qū),根據(jù)參數(shù)innodb_old_blocks_pct來(lái)決定它們的大小,默認(rèn)值3/8為Old區(qū),5/8為Young區(qū);當(dāng)一個(gè)新的數(shù)據(jù)頁(yè)進(jìn)來(lái)的時(shí)候,默認(rèn)的位置是放在old區(qū)的
head,當(dāng)這個(gè)數(shù)據(jù)頁(yè)連續(xù)被訪問(wèn)的時(shí)間超過(guò)1s,會(huì)被移動(dòng)到Y(jié)oung區(qū)的頭部,如果這個(gè)數(shù)據(jù)頁(yè)連續(xù)被訪問(wèn)的時(shí)間小于1s,則位置不變,這個(gè)1s是由參數(shù)innodb_old_blocks_time控制的,這樣做的好處是假如一個(gè)大表全表掃描需要訪問(wèn)很多的數(shù)據(jù)頁(yè),但是每個(gè)數(shù)據(jù)頁(yè)訪問(wèn)的時(shí)間都是很短且訪問(wèn)一次后再次訪問(wèn)的可能性很小,所以放在Old區(qū)能夠更快的淘汰掉釋放空間,不至于讓Buffer Pool中常被訪問(wèn)的熱點(diǎn)數(shù)據(jù)頁(yè)被意外淘汰,能有效提升緩沖池的命中率;
- InnoDB緩沖池里面有什么?
數(shù)據(jù)緩存 - InnoDB數(shù)據(jù)頁(yè)面;
索引緩存 - 索引數(shù)據(jù);
緩沖數(shù)據(jù) - 臟頁(yè): 在內(nèi)存中修改但尚未刷新(寫(xiě)入)到數(shù)據(jù)磁盤的數(shù)據(jù);
內(nèi)部結(jié)構(gòu) - InnoDB緩沖池還存儲(chǔ)內(nèi)部結(jié)構(gòu),如自適應(yīng)哈希索引,行級(jí)鎖等;
相關(guān)配置:
show global status like 'innodb_buffer_pool_read%'
------------------------------------------------
Innodb_buffer_pool_read_requests 57119197605 表示從內(nèi)存中讀取邏輯的請(qǐng)求數(shù)
Innodb_buffer_pool_reads 471363395 表示InnoDB緩沖池?zé)o法滿足的請(qǐng)求數(shù);需要從磁盤中讀取
------------------------------------------------
show global status like 'innodb_buffer_pool_page%'
------------------------------------------------
Innodb_buffer_pool_pages_data 8183 顯示臟和干凈的數(shù)據(jù)和索引頁(yè)面的數(shù)量
Innodb_buffer_pool_pages_dirty 26 顯示在內(nèi)存中修改但尚未寫(xiě)入數(shù)據(jù)文件的InnoDB緩沖池?cái)?shù)據(jù)頁(yè)數(shù)
Innodb_buffer_pool_pages_flushed 2729397 表示從InnoDB緩沖池中刷新臟頁(yè)的請(qǐng)求數(shù)
Innodb_buffer_pool_pages_free 0 顯示InnoDB緩沖池中的空閑頁(yè)面
Innodb_buffer_pool_pages_misc 8 顯示繁忙的頁(yè)面數(shù)
Innodb_buffer_pool_pages_total 8191
------------------------------------------------
推薦的計(jì)算公式 Performance = innodb_buffer_pool_reads / innodb_buffer_pool_read_requests * 100 ,值當(dāng)然是越小越好;
相關(guān)參數(shù):
-
innodb_buffer_pool_size
直接決定了緩沖池的大小,非常重要的配置項(xiàng),MySQL5.7以后可以通過(guò)
set global innodb_buffer_pool_size=xx動(dòng)態(tài)配置,在專用數(shù)據(jù)庫(kù)服務(wù)器上面可以設(shè)置成最大內(nèi)存的50-80%;
show status where variable_name='InnoDB_buffer_pool_resize_status';
可以監(jiān)控在線緩沖池調(diào)整進(jìn)度,也可以在mysql錯(cuò)誤日志中查看;
-
innodb_buffer_pool_instances
buffer pool將劃分為多個(gè)實(shí)例以提高系統(tǒng)并發(fā)性, 減少線程間讀寫(xiě)緩存的爭(zhēng)用。當(dāng)innodb_buffer_pool_size大于 1GB 時(shí),innodb_buffer_pool_instances默認(rèn)為 8;
Change Buffer
當(dāng)對(duì)二級(jí)索引進(jìn)行insert、update、delete時(shí),如果目標(biāo)數(shù)據(jù)頁(yè)在內(nèi)存則直接對(duì)內(nèi)存進(jìn)行操作,但是如果數(shù)據(jù)頁(yè)不在內(nèi)存時(shí),需要把數(shù)據(jù)頁(yè)從磁盤讀入內(nèi)存里面,ChangeBuffer的作用是緩存對(duì)二級(jí)索引的數(shù)據(jù)操作(主鍵索引是用不上的),并且在數(shù)據(jù)頁(yè)被加載進(jìn)buffer pool時(shí)將change buffer中的操作合并,這樣一來(lái)可以避免很多對(duì)磁盤的隨機(jī)IO;另外ChangeBuffer是可以持久化的,意味著數(shù)據(jù)是會(huì)被寫(xiě)在磁盤中,實(shí)際上ChangeBuffer是儲(chǔ)存在ibdata1(系統(tǒng)表空間)里面的;
磁盤一個(gè)I/O請(qǐng)求所花費(fèi)的時(shí)間=尋道時(shí)間+旋轉(zhuǎn)延遲+數(shù)據(jù)傳輸時(shí)間;
順序IO是指讀取和寫(xiě)入操作基于邏輯塊逐個(gè)連續(xù)訪問(wèn)來(lái)自相鄰地址的數(shù)據(jù)。在順序IO訪問(wèn)中,磁盤所需的磁道搜索時(shí)間顯著減少,因?yàn)樽x/寫(xiě)磁頭可以以最小的移動(dòng)訪問(wèn)下一個(gè)塊。
隨機(jī)IO是指讀寫(xiě)操作時(shí)間連續(xù),但訪問(wèn)地址不連續(xù),磁頭在兩次IO操作之間需要作比較大的移動(dòng)動(dòng)作才能重新開(kāi)始讀/寫(xiě)數(shù)據(jù)。
SELECT (SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE
WHERE PAGE_TYPE LIKE 'IBUF%') AS change_buffer_pages,
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE) AS total_pages,
(SELECT ((change_buffer_pages/total_pages)*100))
AS change_buffer_page_percentage;
-------------------------------------
change_buffer_pages 44
total_pages 8191
change_buffer_page_percentage 0.5372
-------------------------------------
Change Buffer使用的是Innodb buffer pool里面的內(nèi)存,所以最大值為buffer pool的大小,使用參數(shù)innodb_change_buffer_max_size來(lái)控制
show variables like 'innodb_change%'
-------------------------------------
innodb_change_buffer_max_size 25
innodb_change_buffering all
-------------------------------------
innodb_change_buffer_max_size
25表示max size為innodb buffer pool的25%
innodb_change_buffering
- none:不使用Change Buffer
- inserts:在insert操作的時(shí)候使用
- deletes:在delete和update操作的時(shí)候使用
- changes:包括inserts和deletes
- purges:在后臺(tái)使用purges的時(shí)候使用
- all:包括inserts、deletes、changes、purges
日志緩存區(qū) log buffer
log buffer儲(chǔ)存要寫(xiě)入磁盤上日志文件的數(shù)據(jù)的內(nèi)存區(qū)域;日志緩沖區(qū)大小由innodb_log_buffer_size變量定義 ;默認(rèn)大小為16MB。日志緩沖區(qū)的內(nèi)容會(huì)定期刷新到磁盤。大的日志緩沖區(qū)使大的事務(wù)能夠運(yùn)行,而無(wú)需在事務(wù)提交之前將重做日志數(shù)據(jù)寫(xiě)入磁盤。因此如果有大量更新,插入或刪除的事務(wù),增加日志緩沖區(qū)的大小可以節(jié)省磁盤I / O。
相關(guān)配置:
-
innodb_flush_log_at_trx_commit
- 0:每秒寫(xiě)入日志并將其刷新到磁盤一次;未刷新日志的事務(wù)可能會(huì)在崩潰中丟失。
- 1:在每次事務(wù)提交時(shí)寫(xiě)入日志并刷新到磁盤
- 2:在每次事務(wù)提交后寫(xiě)入日志,并每秒刷新一次磁盤;未刷新日志的事務(wù)可能會(huì)在崩潰中丟失
InnoDB磁盤結(jié)構(gòu)
Innodb是按照表空間(Tablespace)的結(jié)構(gòu)進(jìn)行儲(chǔ)存的;
共享表空間(System Tablespace)
Innodb的所有數(shù)據(jù)保存在一個(gè)單獨(dú)的表空間里面,而這個(gè)表空間可以由很多個(gè)文件組成,一個(gè)表可以跨多個(gè)文件存在,所以其大小限制不再是文件大小的限制,而是其自身的限制。從Innodb的官方文檔中可以看到,其表空間的最大限制為64TB;
可以在配置文件中找到datadir所配置的路徑,默認(rèn)的共享表空間文件名叫ibdata1;
獨(dú)立表空間(File-Per-Table Tablespace)
每個(gè)InnoDB表都存儲(chǔ)在自己獨(dú)立的表空間數(shù)據(jù)文件。.ibd文件儲(chǔ)存數(shù)據(jù)和索引,.frm儲(chǔ)存表結(jié)構(gòu)數(shù)據(jù);
不同處:
truncate或者drop儲(chǔ)存在獨(dú)立表空間的表時(shí)能回收磁盤空間,而truncate或者drop儲(chǔ)存在系統(tǒng)表空間的表時(shí)會(huì)在ibdata文件內(nèi)部標(biāo)記可用空間,并不會(huì)縮小文件大??;
獨(dú)立表空間可以使用optimize table來(lái)壓縮或重新創(chuàng)建表文件;當(dāng)運(yùn)行這條命令時(shí),InnoDB創(chuàng)建一個(gè)新的.ibd具有臨時(shí)名稱的文件,該文件存儲(chǔ)的實(shí)際數(shù)據(jù)是該表所需要的空間。優(yōu)化完成后,InnoDB刪除舊.ibd文件并將其替換為新文件。
雙寫(xiě)緩存區(qū)(Doublewrite Buffer)
首先需要理解一些概念,磁盤物理操作的基本單位是扇區(qū),是真實(shí)存在的物理單位,在linux下使用fdisk -l命令可以了解該磁盤的基本信息比如

操作系統(tǒng)與磁盤之間數(shù)據(jù)交流的最小單位是磁盤塊,是操作系統(tǒng)抽象出來(lái)的一個(gè)概念;linux內(nèi)核要求 block size = sector size * 2^n,即是扇區(qū)的整數(shù)倍;linux可以通過(guò)
tune2fs -l /dev/vda1|grep Block size查看block size大小(一般是Block size: 4096),操作系統(tǒng)是以頁(yè)管理的,一般大小為4k;
而在Innodb里面,page是存儲(chǔ)的最基本結(jié)構(gòu),也是對(duì)磁盤管理的最小單位;可以使用show variables like 'innodb_page_size' 查看,一般為16k;
任何數(shù)據(jù)庫(kù)IO操作最終都是體現(xiàn)在對(duì)扇區(qū)的IO操作上面,比如現(xiàn)在需要對(duì)一頁(yè)(16k)的數(shù)據(jù)寫(xiě)入磁盤,因?yàn)榇疟P扇區(qū)的大小為512bytes,所以是需要對(duì)多個(gè)扇區(qū)進(jìn)行操作,當(dāng)這個(gè)操作進(jìn)行到一半時(shí)發(fā)生數(shù)據(jù)庫(kù)宕機(jī)或者掉電,就會(huì)出現(xiàn)數(shù)據(jù)頁(yè)只有部分寫(xiě)入磁盤,這種情況叫做頁(yè)斷裂(partial write);
Doublewrite buffer是Innodb表空間內(nèi)部分配的一片緩沖區(qū)且與數(shù)據(jù)頁(yè)一樣有物理存儲(chǔ)空間,儲(chǔ)存在共享表空間中。當(dāng)臟頁(yè)的數(shù)據(jù)flush到磁盤時(shí)先將數(shù)據(jù)寫(xiě)入doublewrite buffer中,然后在fsync到磁盤上,如果期間發(fā)生掉電并且導(dǎo)致page數(shù)據(jù)損壞,可以通過(guò)buffer內(nèi)的數(shù)據(jù)進(jìn)行恢復(fù);因?yàn)閷?xiě)入doublewrite buffer和數(shù)據(jù)頁(yè)落盤的時(shí)間點(diǎn)是不一樣的所以不會(huì)出現(xiàn)兩個(gè)都是損壞的情況;
當(dāng)然雙寫(xiě)會(huì)帶來(lái)一定的消耗,但是不是兩倍于直接落盤,因?yàn)閿?shù)據(jù)頁(yè)的數(shù)據(jù)是做為一個(gè)大的連續(xù)的塊(chunk)順序?qū)懭腚p寫(xiě)緩存區(qū)中,只需要一次fsync;
重做日志(Redo Log)
InnoDB記錄了對(duì)數(shù)據(jù)文件的物理更改,并保證總是日志先行,也就是所謂的WAL(Write-Ahead Logging),即在持久化數(shù)據(jù)文件前,保證之前的redo日志已經(jīng)寫(xiě)到磁盤。redo log默認(rèn)在磁盤上由兩個(gè)名為ib_logfile0和ib_logfile1(默認(rèn)是兩個(gè))的文件物理表示,MySQL以循環(huán)方式寫(xiě)入這兩個(gè)文件;
- 為什么需要redo log
當(dāng)修改innodb表上某行數(shù)據(jù)時(shí),如果該行不在內(nèi)存中則需要將該數(shù)據(jù)頁(yè)從磁盤讀入到內(nèi)存去然后在內(nèi)存中更新該行,現(xiàn)在內(nèi)存中的數(shù)據(jù)頁(yè)與磁盤中就不一致了,把這種內(nèi)存數(shù)據(jù)頁(yè)與磁盤數(shù)據(jù)頁(yè)不一致的數(shù)據(jù)頁(yè)稱為臟頁(yè)(dirty page),DB需要把臟頁(yè)數(shù)據(jù)寫(xiě)入磁盤,但是如果每一次更新數(shù)據(jù)就會(huì)帶來(lái)一次磁盤操作的話那么機(jī)器肯定撐不??;所以說(shuō)mysql會(huì)把標(biāo)記為臟頁(yè)的數(shù)據(jù)頁(yè)儲(chǔ)存在一個(gè)flush list里面,用一個(gè)專門的后臺(tái)線程定時(shí)刷臟;那么如果在mysql還沒(méi)有刷臟的時(shí)候數(shù)據(jù)庫(kù)掛了怎么辦呢,所以就需要redo log; - 儲(chǔ)存著什么
由日志緩存區(qū)(redo log buffer)和日志文件(redo log file)構(gòu)成,redo log是物理日志,保存的是數(shù)據(jù)頁(yè)上被修改的值;還有一種是邏輯日志,保存的是SQL語(yǔ)句比如binlog;

一開(kāi)始是存在于
redolog buffer,這部分是屬于mysql進(jìn)程內(nèi)存中的,所以寫(xiě)這部分的效率是很高的;在一個(gè)事務(wù)的更新過(guò)程中redolog可能是要寫(xiě)多次的,所以在commit前日志是保存在redolog buffer中,等待commit時(shí)在寫(xiě)到redolog file中;
其實(shí)寫(xiě)redolog file也不是直接寫(xiě)到磁盤,是寫(xiě)到文件系統(tǒng)的page cahce里面,最后才是持久化到磁盤,fsync一般來(lái)說(shuō)是IO性能的瓶頸;
page cache用來(lái)緩存文件數(shù)據(jù),是屬于操作系統(tǒng)的緩存,如果想要持久化到磁盤中需要使用fsync;
除了后臺(tái)線程會(huì)每秒刷一次之外,還有兩種情況會(huì)主動(dòng)寫(xiě)盤;
-
redo log buffer即將占用innodb_log_buffer_size的一半的時(shí)候,后臺(tái)線程會(huì)主動(dòng)寫(xiě)盤,但是也只是寫(xiě)入到page cache里面并沒(méi)有使用fsync; - 當(dāng)
innodb_flush_log_at_trx_commit是1的時(shí)候,如果并行的事務(wù)提交,那么會(huì)將redo log buffer的所有日志(包括其他線程只寫(xiě)到一半的日志)都會(huì)持久化到磁盤,因?yàn)閞edo log buffer是共享的;
show variables like 'innodb_log%'
-------------------------------------
innodb_log_buffer_size 16777216 寫(xiě)入日志文件緩沖區(qū)的字節(jié)
innodb_log_file_size 50331648 ib_logfile文件的大小
innodb_log_files_in_group 2 控制日志文件數(shù)
-------------------------------------
參考
How to allocate innodb_buffer_pool_size in MySQL?
頁(yè)斷裂(partial write)與doublewrite技術(shù)
IO系統(tǒng)性能之一:衡量性能的幾個(gè)指標(biāo)
磁盤I/O那些事
MySQL · 引擎特性 · InnoDB redo log漫游
詳細(xì)分析MySQL事務(wù)日志(redo log和undo log)
MySQL · 引擎特性 · Innodb change buffer介紹
