對於提供事務支持的數據庫,在事務提交時,都要確保事務日誌(包含該事務所有的修改操作以及一個提交記錄)完全寫到硬盤上,才認定事務提交成功並返回給應用層。
一個簡單的問題:在*nix操作系統上,怎樣保證對文件的更新內容成功持久化到硬盤?
1. write不夠,需要fsync
1 #include <unistd.h>
2 int fsync(int fd);
1 #incude <sys/mman.h>
2 int msync(void *addr, size_t length, int flags)
msync需要指定同步的地址區間,如此細粒度的控制似乎比fsync更加高效(因爲應用程序通常知道自己的髒頁位置),但實際上(Linux)kernel中有着十分高效的數據結構,能夠很快地找出文件的髒頁,使得fsync只會同步文件的修改內容。
2. fsync的性能問題,與fdatasync
"Unfortunately fsync() will always initialize two write operations : one for the newly written data and another one in order to update the modification time stored in the inode. If the modification time is not a part of the transaction concept fdatasync() can be used to avoid unnecessary inode disk write operations."
多餘的一次IO操作,有多麼昂貴呢?根據Wikipedia的數據,當前硬盤驅動的平均尋道時間(Average seek time)大約是3~15ms,7200RPM硬盤的平均旋轉延遲(Average rotational latency)大約爲4ms,因此一次IO操作的耗時大約爲10ms左右。這個數字意味着什麼?下文還會提到。
Posix同樣定義了fdatasync,放寬了同步的語義以提高性能:
1 #include <unistd.h>
2 int fdatasync(int fd);
"fdatasync does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be corretly handled."
3. 使用fdatasync優化日誌同步
在Berkeley DB下,如果開啓了AUTO_COMMIT(所有獨立的寫操作自動具有事務語義)並使用默認的同步級別(日誌完全同步到硬盤才返回),寫一條記錄的耗時大約爲5~10ms級別,基本和一次IO操作(10ms)的耗時相同。
1.每個log文件固定爲10MB大小,從1開始編號,名稱格式爲“log.%010d"2.每次log文件創建時,先寫文件的最後1個page,將log文件擴展爲10MB大小3.向log文件中追加記錄時,由於文件的尺寸不發生變化,使用fdatasync可以大大優化寫log的效率4.如果一個log文件寫滿了,則新建一個log文件,也只有一次同步metadata的開銷