數據庫作為存儲系統,所有業務訪問數據的操作都會轉化為底層數據庫系統的IO行為(緩存系統也可以當做是key-value的數據庫),本文主要介紹訪問MySQL數據庫的IO流程以及IO相關的參數。
一、MySQL的文件
首先簡單介紹一下MySQL的數據文件,MySQL 數據庫包含如下幾種文件類型:
1)數據文件 (datafile)
存放表中的具體數據的文件。
2)數據字典
記錄數據庫中所有innodb表的信息。
3)重做日志 (redolog)
記錄數據庫變更記錄的文件,用於系統異常crash(掉電)后的恢復操作,可以配置多個(配置這個參數innodb_log_files_in_group)比如 ib_logfile0、 ib_logfile1。
4)回滾日志 (undolog)
也存在於mysql 的ibdata文件,用戶記錄事務的回滾操作。注在mysql5.6以上版本可以拆開出來,單獨文件夾存在。
5)歸檔日志 (binlog)
事務提交之后,記錄到歸檔日志中。
6)中繼日志 (relaylog)
從master獲取到slave的中轉日志文件,sql_thread則會應用relay log並重放於從機器。
7)其他日志slowlolg, errorlog, querylog
這里慢日志也經常用。可以結合pt-query-digest工具和anemometer一起展示出來。
對於以上文件的IO訪問順序可以分為順序訪問 比如binlog ,redolog ,relay log是順序讀寫,datafile,ibdata file是隨機讀寫,這些IO訪問的特點決定了在os 配置磁盤信息時候,如何考慮分區 ,比如順序寫可以的log可以放到SAS盤 ,隨機讀寫的數據文件可以放到ssd或者fio高性能的存儲。
二、數據訪問流程
數據庫訪問分為兩種類型, 一種是讀操作,另外一種是寫操作。
- 讀操作
1
2
3
4
5
6
|
create table t (
id int not null primary key ,
k1 int not null,
data varchar(50),
key ind_k1(k1)
) engine=innodb default charset=utf8;
|
以select * from tab where k1=1 ;為例。
讀操作的IO流程
1、查看緩存中是否存在id,
2、如果有則從內存中訪問,否則要訪問磁盤,
3、並將索引數據存入內存,利用索引來訪問數據,
4、對於數據也會檢查數據是否存在於內存,
5、如果沒有則訪問磁盤獲取數據,讀入內存。
6、返回結果給用戶。
- 寫操作
為了保證數據寫入操作的安全性,數據庫系統設置了 undo,redo 保護機制,避免因為os或者數據庫系統異常導致的數據丟失或者不一致的異常情況發生。
以insert into t values(1,1,’shuiyi’);為例。
寫操作的IO流程
我們假定數據在內存中,不考慮從磁盤中獲取數據的情形,大致的寫操作步驟:
1、先寫undo log。
2、在內存更新數據,這步操作就在內存中形成了臟頁,如果臟頁過多,checkpoint機制進行刷新,innodb_max_dirty_pages_pct決定了刷新臟頁比例。innodb_io_capacity參數可以動態調整刷新臟頁的數量,innodb_lru_scan_depth這個參數決定了刷新每個innodb_buffer_pool的臟頁數量。
3、記錄變更到redo log,prepare這里會寫事務id。innodb_flush_log_at_trx_commit決定了事務的刷盤方式。為0時,log buffer將每秒一次地寫入log file中,並且log file的flush(刷到磁盤)操作同時進行。該模式下,在事務提交的時候,不會主動觸發寫入磁盤的操作。為1,每次事務提交時MySQL都會把log buffer的數據寫入log file,並且flush(刷到磁盤)中去.為2,每次事務提交時MySQL都會把log buffer的數據寫入log file.但是flush(刷到磁盤)操作並不會同時進行。該模式下,MySQL會每秒執行一次 flush(刷到磁盤)操作。
4、寫入binlog這里會寫入一個事務id這里有個sync_binlog參數決定多個事務進行一次性提交。
5、redo log第二階段,這里會進行判斷前2步是否成功,成功則默認commit,否則rollback。刷入磁盤操作。這里是先從臟頁數據刷入到內存2M大小的doublewrite buffer,然后是一次性從內存的doublewrite buffer刷新到共享表空間的doublewrite buffer,這里產生了一次IO。然后從內存的內存的doublewrite buffer刷新2m數據到磁盤的ibd文件中,這里需要發生128次io。然后校驗,如果不一致,就由共享表空間的副本進行修復。這里有個參數innodb_flush_method決定了數據刷新直接刷新到磁盤,繞過os cache。
6、返回給client。
如果有slave,第4步之后經過slave服務線程io_thread寫到從庫的relay log ,再由sql thread應用relay log到從庫中。
關於性能?
寫undo redo log ,binlog的過程中都是順序寫,都會很快的完成,隨機寫操作,inset_buffer功能。
對於非聚集類索引的插入和更新操作(5.5 版本及以上支持Update/Delete/Purge等操作的buffer功能),不是每一次都直接插入到索引頁中,而是先插入到內存中。具體做法是:如果該索引頁在緩沖池中,直接插入;否則,先將其放入插入緩沖區中,再以一定的頻率和索引頁合並,就可以將同一個索引頁中的多個插入合並到一個IO操作中,改隨機寫為順序寫,大大提高寫性能。
關於數據安全,這是數據庫寫入的重點?
1,2,3過程失敗就是事務失敗,因為此時還未寫入磁盤,對磁盤中的數據無影響,返回事務失敗給client,從庫也不會受到影響。 4,5過程失敗的時候或者已經將寫成功返回給客戶,可以根據redo log的記錄來進行恢復,如果出現部分寫失效請參考《double write》。
MySQL的寫redo log的第一個階段會把所有需要做的操作做完,記錄數據變更,第二階段的工作比較簡單 ,只做事務提交確認。如果寫入binlog成功,而第二階段失敗,MySQL啟動時也會將事務進行重做,最終更新到磁盤中。MySQL 5.5+的smei sync可以更好的保障主從的事務一致性。
三、文件訪問方式
IO 訪問的方式分為兩種順序讀寫和隨機讀寫, 在MySQL的io過程中可以以此來將數據庫文件分類
順序讀寫:重做日志ib_logfile*,binlog file。
隨機讀寫:innodb表數據文件,ibdata文件。
根據系統的訪問類型,對硬件做如下分類:讀多(SSD+RAID)、寫多FIO(flashcache)、容量密集(fio + flashcache)。
由於隨機io會嚴重降低系統的性能,在當前的硬件水平下,可以考慮選擇獎數據庫服務器配置ssd/fusionio。
四、影響IO的參數和策略
影響mysql io的參數有很多個,這里羅列幾個重要的參數。
innodb_buffer_pool_size
該參數控制innodb緩存大小,用於緩存應用訪問的數據,推薦配置為系統可用內存的80%。
binlog_cache_size
該參數控制二進制日志緩沖大小,當事務還沒有提交時,事務日志存放於cache,當遇到大事務cache不夠用的時,mysql會把uncommitted的部分寫入臨時文件,等到committed的時候才會寫入正式的持久化日志文件。
innodb_max_dirty_pages_pct
該參數可以直接控制Dirty Page在BP中所占的比率,當dirty page達到了該參數的閾值,就會觸發MySQL系統刷新數據到磁盤。
innodb_flush_log_at_trx_commit
該參數確定日志文件何時write、flush。
為0,log buffer將每秒一次地寫入log file中,並且log file的flush(刷到磁盤)操作同時進行.該模式下,在事務提交的時候,不會主動觸發寫入磁盤的操作。
為1,每次事務提交時MySQL都會把log buffer的數據寫入log file,並且flush(刷到磁盤)中去.
為2,每次事務提交時MySQL都會把log buffer的數據寫入log file.但是flush(刷到磁盤)操作並不會同時進行。該模式下,MySQL會每秒執行一次 flush(刷到磁盤)操作。
注意:由於進程調度策略問題,這個“每秒執行一次flush(刷到磁盤)操作”並不是保證100%的“每秒”。
sync_binlog
sync_binlog的默認值是0,像操作系統刷其他文件的機制一樣,MySQL不會同步到磁盤中去而是依賴操作系統來刷新binary log。
當sync_binlog =N (N>0) ,MySQL 在每寫 N次 二進制日志binary log時,會使用fdatasync()函數將它的寫二進制日志binary log同步到磁盤中去。
innodb_flush_method
該參數控制日志或數據文件如何write、flush。可選的值為fsync,o_dsync,o_direct,littlesync,nosync。
數據庫的I/O是一個很復雜和細致的知識層面,涉及數據庫層和OS層面的IO寫入策略,也和硬件的配置有關,更詳細看MySQL InnoDB磁盤I/O優化。