一文講清MySQL的innodb_log_write_ahead_size參數(shù)
一文講清MySQL的innodb_log_write_ahead_size參數(shù)
MySQL調(diào)優(yōu)的時候會遇到一個參數(shù)innodb_log_write_ahead_size,這個參數(shù)如果對計算機(jī)存儲系統(tǒng)不了解的話很難理解,網(wǎng)上很多文章說的又不是很清晰,所以本文對該參數(shù)做一個解析;
要想知道innodb_log_write_ahead_size參數(shù)怎么配置,最重要的就是先了解這個參數(shù)解決了什么問題,那么這個參數(shù)是解決什么問題的呢?官網(wǎng)對該參數(shù)的描述 如下:
Defines the write-ahead block size for the redo log,
in bytes. To avoid “read-on-write”, set innodb_log_write_ahead_size
to match the operating system or file system cache block
size. The default setting is 8192 bytes. Read-on-write occurs
when redo log blocks are not entirely cached to the operating
system or file system due to a mismatch between write-ahead
block size for the redo log and operating system or file system
cache block size.
譯文:
為了避免read-on-write,為redo log定義了write-ahead block size,
單位byte,請將innodb_log_write_ahead_size參數(shù)的大小設(shè)置為操作系
統(tǒng)或者文件系統(tǒng)的緩存塊(對應(yīng)的就是page cache),默認(rèn)設(shè)置大小是8K,如果該參數(shù)大小設(shè)置的與page cache不匹配那么將會發(fā)生Read-on-write;
翻譯過來每個字都認(rèn)識,但是這些字連起來是什么意思呢?如果懂這段話的意思,說明你對文件系統(tǒng)有一定的了解,那么后邊的內(nèi)容大概率你已經(jīng)知道了,如果 不懂,那么后面就是解釋這句話的意思;
首先,上邊提到的了innodb_log_write_ahead_size主要是解決read-on-write問題的,看來我們需要先知道什么是read-on-write,read-on-write 的描述如下(個人理解,如果錯各位大佬可以指出):
現(xiàn)代操作系統(tǒng)對磁盤的最小操作單位是page,每次讀取、寫出都
是按照一個page一個單位操作的,讀取的時候沒有特別的問題,但是在寫出的時候會有問題,如果要寫出的數(shù)據(jù)所在的磁盤上
的page(邏輯)沒有在內(nèi)存中,那么需要先將磁盤上該page的
數(shù)據(jù)加載到內(nèi)存然后才能寫出,這個就叫做read-on-write;
read-on-write的簡要流程圖:

從流程中很容易看出發(fā)生read-on-write的時候系統(tǒng)會比沒有read-on-write的時候多一次read IO,相當(dāng)于原來只需要一次write IO的現(xiàn)在變成了先read IO 然后在write IO,一下工作量翻倍,單這一個點就造成了我們系統(tǒng)速度下降一半(毛估,還有很多其他因素);如果想要針對這點優(yōu)化,只要我們不讓這次read IO發(fā)生 不就行了?那如何不讓這次read IO發(fā)生呢?有兩種辦法:
確保寫出數(shù)據(jù)所在的磁盤page已經(jīng)緩存在內(nèi)存中了,而要確保這個只能先進(jìn)行一次read將數(shù)據(jù)緩存到內(nèi)存,這跟上邊的read-on-write差不多了,只不過是我們手 動調(diào)用了一次read IO,雖然沒有發(fā)生系統(tǒng)層面的read-on-write,但實際上是等效的,所以這個方案不行;
利用系統(tǒng)的另外一個特性,即如果IO滿足以下兩個條件則也不會發(fā)生
read-on-write:該IO的目的地址是磁盤上某個page的起始偏移地址;
該IO的數(shù)據(jù)大小是page大小的整數(shù)倍;
為什么如果沒有緩存時必須發(fā)生一次read-on-write呢?其實很簡單,因為系統(tǒng)寫出的最小單位是page,如果不先把磁盤上指定區(qū)域的內(nèi)容加載上來,那么在本次 寫出的時候會將舊數(shù)據(jù)覆蓋了,這樣就可能會導(dǎo)致數(shù)據(jù)丟失,所以只能將原有數(shù)據(jù)加載上來,然后與本次要寫出的數(shù)據(jù)合并然后一次寫出;而第二個方案的前提條件 其實就是如果你的數(shù)據(jù)正好把這個page整個都覆蓋了,那么無論內(nèi)存中是否有該page的緩存,都可以直接寫磁盤,因為你就是要覆蓋磁盤上整個page的區(qū)間,不 存在丟失原有數(shù)據(jù)的問題;
第二個方案就比較好實現(xiàn)了,我們不用額外的IO就能讓我們的寫出數(shù)據(jù)滿足該條件,而MySQL中用的也是該方案,而innodb_log_write_ahead_size就是我們告訴 MySQL我們的系統(tǒng)實際的page size是多少,該值最小要等于page size,如果比page size小就可能會發(fā)生read-on-write,可以比實際的page size大,但 是必須是page size的整數(shù)倍;默認(rèn)值是8K,一般大多數(shù)系統(tǒng)的page size都是4K,所以大多數(shù)情況下默認(rèn)值是可以避免read-on-write發(fā)生的;當(dāng)MySQL要寫 出數(shù)據(jù)時,如果寫出數(shù)據(jù)小于innodb_log_write_ahead_size則會在后邊補(bǔ)0,然后將整個innodb_log_write_ahead_size的數(shù)據(jù)一次性寫出,使他滿足上邊 的兩個不觸發(fā)read-on-write機(jī)制的條件從而達(dá)到優(yōu)化IO提高性能的目的(第一個條件MySQL可以控制滿足,第二個條件是否真的能滿足就看我們配置的 innodb_log_write_ahead_size 的值,如果配置的不符合條件則也不會);
不加write_ahead機(jī)制的寫出示意圖:




加了write_ahead機(jī)制的寫出示意圖:


innodb_log_write_ahead_size解決什么問題現(xiàn)在已經(jīng)很清楚了,那么到底配多少合適呢?上邊只是說了最小要等于文件系統(tǒng)的的page size,也可以是page size 的整數(shù)倍,那調(diào)整大點兒會有影響嘛?根據(jù)上邊的圖示可以看出,無論我們寫出多少數(shù)據(jù),MySQL都會將整個write_ahead_buffer寫出,也就是如果write_ahead_buffer 值太大的話可能會對系統(tǒng)IO有輕微的影響,畢竟本來只需要寫出一個page就可以的結(jié)果因為配置的過大導(dǎo)致寫了兩個page甚至更多,還是會有輕微影響的(實測影響確 實很輕微,配置2-4倍幾乎是無影響的 ),官網(wǎng)對于這個也是有說明的,說明如下:
Setting the innodb_log_write_ahead_size value too low
in relation to the operating system or file system cache
block sizeresults in “read-on-write”. Setting the valuetoo high may have a slight impact on fsync performance
for log file writesdue to several blocks being written
at once.
思考
MySQL可以使用這個優(yōu)化跟MySQL的文件結(jié)構(gòu)有關(guān),MySQL寫出是append write而不是random write的,這個很重要,如果是random write那么這個策略就會 失效,各位可以作為一個拓展思考回去自己想下,有知道原因的可以在評論區(qū)打出來;
聯(lián)系我
作者微信:JoeKerouac
微信公眾號(文章會第一時間更新到公眾號,如果搜不出來可能是改名字了,加微信即可=_=|):Java初學(xué)者
GitHub:https://github.com/JoeKerouac
參考文獻(xiàn)
innodb_log_write_ahead_size官方解釋:https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_log_write_ahead_size
