Sqlite學習筆記(三)&&WAL性能測試中列出了幾種典型場景下WAL的性能數據,了解到WAL確實有性能優勢,這篇文章將會詳細分析WAL的原理,做到知其然,更要知其所以然。
WAL是什么
WAL(Write ahead logging)是一種日志模式,它是一種思想,普遍應用於關系型數據庫。每個事務執行變更時,修改數據頁,同時會產生日志,這樣在事務提交后,不需要將修改的臟頁刷盤,只需要將事務產生的日志落盤即可返回。WAL保證日志一定先於對應的臟頁落盤,就是所謂的WAL。SQLITE在3.7版本以后引入WAL,它的WAL也基本采用這個原理,只不過SQLite實現比較簡單,日志記錄的是修改后的頁,而不是所謂的修改日志。WAL模式下,SQlite中除了db文件,還包含了兩個文件,.wal文件和.shm文件,前者是日志文件,后者是日志索引文件。
日志模式
SQLite中日志模式主要有DELETE和WAL兩種,其他幾種比如TRUNCATE,PERSIST,MEMORY基本原理都與DELETE模式相同,不作詳細展開。DELETE模式采用影子分頁技術(Shadow paging),DELETE模式下,日志中記錄的變更前數據頁內容;WAL模式下,日志中記錄的是變更后的數據頁內容。事務提交時,DELETE模式將日志刷盤,將DB文件刷盤,成功后,再將日志文件清理;WAL模式則是將日志文件刷盤,即可完成提交過程。那么WAL模式下,數據文件何時更新呢?這里引入了檢查點概念,檢查點的作用就是定期將日志中的新頁覆蓋DB文件中的老頁,並通過參數wal_autocheckpoint來控制檢查點時機,達到權衡讀寫的目的。
DELETE模式下,寫事務直接更新db-page,並將old-page寫入日志,讀事務則直接讀db-page,因為db-page中保存了提交的所有事務的更新。事務提交后,直接將日志文件刪除;若事務需要回滾,則將日志中old-page中的內容覆蓋db-page,恢復原始內容。WAL模式下,寫事務將更新寫到日志文件中,不更新db-page,事務提交時,也不影響db-page,只是將日志持久化而已。若事務回滾,則不將日志寫入文件即可。由於最新的數據在日志文件中,那么如何讀取到最新的數據呢?WAL模式通過end-mark(事務提交位點)達到這一目的。具體而已,事務開始時,會首先掃描日志文件,獲取最近一個end-mark,在讀取數據時,首先會判斷page是否則在wal日志文件中存在,因為同一個page,一定是wal文件中的比db文件中的要新。如果存在,則使用,否則,再從db文件中獲取指定的page。從流程上來看,這個過程比較慢,因為極端情況下,每次讀都需要掃描wal文件和db文件。為了提高性能,WAL模式中有一個wal-index文件,這個文件記錄了頁號和該頁在WAL文件中的偏移,並且wal-index文件采用共享緩存實現,從文件名也可以看到,后綴是.shm,因此判斷page是否在wal文件存在的操作實質是一次內存讀。wal-index采用hash表存儲,因此查詢效率也非常高。
與傳統的DBMS不同,SQLite中記錄的日志,實質是dirty-page,重做實質是對利用WAL中的日志頁覆蓋db-page,這種實現方式比較簡單,同時也比較浪費空間,因為一個page是1k,即使只更新1byte,也會導致日志記錄1k。
WAL的優勢與劣勢
1) 並發優勢
SQLite為什么引入WAL,一定是WAL有很多好的特性。其中最主要的一點是WAL支持讀寫並發。在DELETE模式下,讀寫是互斥的。為什么WAL可以並發,而DELETE不行?我這里不打算詳細展開WAL模式和DELETE模式的鎖機制,后面有機會再單獨寫這一部分。從上面一節的分析可以知道,WAL模式下,寫事務以append方式記錄new-page,而讀事務只會讀取db-page和end-mark之前的wal日志,因此不會發生讀寫沖突的問題,讀寫可以並發。而DELETE模式下,寫事務寫的是db-page,讀事務也是讀db-page,所以讀寫不能並發。
2) 寫性能優勢
從前面的分析可知,WAL模式下,事務提交只需要寫入日志文件即可,為了持久化,只需要一次fsync調用。而DELETE模式下,事務提交過程中,首先要確保日志落盤(保存old-page,用來rollback),這里需要一次fsync調用,然后再執行db文件刷盤,這里還需要一次fsync,並且修改的db-page可能是離散的,也會影響性能。而WAL寫日志都是順序寫,相對於離散寫又有很大的優勢。因此DELETE模式下寫性能會比WAL模式要差。測試結果也證明了這一點,這里可以參考測試報告。
3) WAL劣勢
開啟WAL后,每次讀取page,都需要通過wal-index來確認page是否在WAL中,這個會產生一定的性能損耗。另外,會引入WAL文件,這個文件如果使用不當,可能會急劇膨脹,WAL文件變大后,意味着檢索wal-index的代價也變高。而且由於SQLite一般用於端設備,空間也比較稀缺,因此要嚴格控制好WAL文件的大小。此外,WAL的索引文件采用共享內存實現,因此訪問SQlite的進程不能跨機器。
開啟WAL模式
通過命令pragma journal_mode=wal可以開啟wal模式。前面我們提到開啟WAL模式后,如果使用不當,可能導致WAL文件空間暴增,但我們有辦法避免這種情況發生。這里主要介紹兩個參數,wal_autocheckpoint和journal_size_limit。wal_autocheckpoint用來設置觸發檢查點的時機,默認是1000頁,即當日志增長到1000頁時,開始做檢查點操作。這里要說明一點的是,SQLite中沒有單獨的檢查點線程,如果設置1000,則觸發寫1000頁的事務來進行檢查點操作。因此這個事務的響應時間會比較長,而其它事務則不受影響。用來設置日志文件的大小,默認情況為-1,當這個參數設置時,若累計更新頁大小超過journal_size_limit,也會導致檢查點觸發,用以重復利用日志文件,避免日志繼續增長。
問題
1. WAL模式下,檢查點是否會導致鎖等待?
檢查點包括自動檢查點和手動檢查點。通過PRAGMA wal_autocheckpoint=N命令,可以設置自動檢查點,當N<=0時,自動檢查點關閉,所有自動檢查點的類型都是PASSIVE。默認情況下,自動檢查點是開啟的,N為1000。對於PASSIVE類型的檢查點,不會影響讀寫事務。通過 PRAGMA schema.wal_checkpoint 命令可以手工觸發一次檢查點,比如PRAGMA schema.wal_checkpoint(PASSIVE)觸發一次PASSIVE類型檢查點,PRAGMA schema.wal_checkpoint(FULL)觸發一次FULL類型檢查點。對於FULL類型,由於需要將所有更新合並到DB文件,如果有讀寫事務沒有結束,則需要等待;而且做檢查點過程中,會堵塞新的讀寫事務。所以PASSIVE類型不會導致鎖等待,而FULL類型,RESTART和TRUNCATE類型,會導致鎖等待。
2. 檢查點是否會導致事務響應時間變長?
對於自動檢查點,根據wal_autocheckpoint=N設置,當更新page超過N時會觸發一次檢查點,那么當前的這個事務就需要等檢查點執行完畢才返回,所以觸發檢查點的事務響應時間會變長。
3.WAL隔離級別是什么?
WAL模式下,讀寫可以並發,事務能否獲得新數據的關鍵點在於wal-index的位點,SQLite中,只會在每個事務開始時獲取一次位點,事務中多次讀位點都是同一個,因此隔離級別是可重復讀。
相關參數
1) journal_mode(日志模式)
默認是DELETE模式
DELETE:原始數據頁存放在日志文件中,事務提交時,將文件刪除。
TRUNCATE :與DELETE模式的區別是,清空日志文件,但不刪除文件清空文件往往比刪除文件要快。
PERSIST:與DELETE和TRUNCATE模式區別是,既不刪除文件,也不清空文件,而是將日志文件第一個頁設置標記(置0),這個也是為了提高性能。
MEMORY :內存模式,修改不落盤,無法保證事務的原子性。
OFF:不開啟日志,這樣沒法保證事務的原子性。
WAL :write ahead log,3.7.0引入,日志中記錄修改頁,提交時只需刷修改頁。
2) journal_size_limit(日志文件大小)
默認值為-1,表示沒有限制,單位是字節。
DELETE模式下,當日志增長超過閥值時,則進行截斷。
WAL模式下,當日志增長超過閥值時,日志文件不再會被截斷,而是重復利用,
因為通常情況下重復寫的性能要好於追加的性能,而且也省磁盤空間。
default_journal_size_limit,用於設置日志文件的默認大小。
3) wal_checkpoint(檢查點模式)
PASSIVE,默認自動檢查點和主動檢查點都是PASSIVE類型,將所有可以同步到db的數據都進行同步(不超過所有線程的end mark),不持有排它鎖,因此不會影響其他讀寫事務。
FULL,將wal與db文件完全同步,需要等待所有讀寫事務都結束,並且會堵塞新的讀寫事務
RESTART,與FULL模式的區別是,下一個寫線程從頭開始寫wal文件。
TRUNCATE,與FULL模式的區別是,將wal文件截斷為0。
4) wal_autocheckpoint(檢查點觸發時機)
默認值為1000頁,單位是頁。當日志的增量到N頁時,觸發檢查點操作,將wal_autocheckpoint設置為0或者-1,表示關閉檢查點。
5) synchronous(同步模式)
默認設置是FULL
0(OFF):事務提交時,不作sync操作,直接返回。
1(NORMAL):事務提交時,日志頭不作sync操作
2(FULL):每次事務提交,都強制刷日志(WAL),強制數據頁(journal)
6) cache_size
默認值2000,單位為頁
修改db的緩存頁數目,臨時生效
7) default_cache_size
默認值2000,單位為頁
修改緩存頁數目,永久生效若同時設置了cache_size和default_cache_size,以default_cache_size為准
參考文檔
https://www.sqlite.org/wal.html