SQLite資料庫交易處理詳解教程

來源:互聯網
上載者:User

說到事務一定會提到ACID,所謂事務的原子性,一致性,隔離性和持久性。對於一個資料庫而言,通常通過並發控制和故障恢複手段來保證事務在正常和異常情況下的ACID特性。sqlite也不例外,雖然簡單,依然有自己的並發控制和故障恢複機制。Sqlite學習筆記(五)&&SQLite封鎖機制 已經講了一些鎖機制的原理,本文將會詳細介紹一個事務從開始,到執行,最後到提交所經曆的過程,其中會穿插講一些sqlite中鎖管理,緩衝管理和日誌管理的機制,同時會介紹在異常情況下(軟硬體故障,比如程式異常crash,主機掉電等),sqlite如何將資料庫恢複到事務之前的狀態。本文大量參考了 sqlite的官方文檔,結合自己的理解,希望能把這個過程說清楚。

1.事務提交

1.1 開啟一個事務

在向資料庫檔案寫資料前,sqlite首先需要訪問sqlite_master表擷取中繼資料資訊,用來對SQL語句進行語義分析,判斷語句的合法性。從資料庫讀資料第一步,是對資料庫檔案上一個Shared Lock。Shared Lock允許多個事務同時讀一個資料庫檔案,但是Shared Lock會阻止寫事務向資料庫檔案寫入資料。


1.2 讀資料

擷取Shared Lock後,我們可以從資料庫檔案中讀取資料了。我們假設緩衝中沒有我們的page,因此需要通過讀檔案讀取我們需要的page。這裡說明下,sqlite的資料庫檔案實質是有一個個大小相同的page組成,預設情況下,一個page大小為1024B。通常情況下,我們需要讀取若干個 page,並把這些page緩衝在應用本地的cache中,這樣下次訪問就不需要再次從檔案中讀取。這裡我們假設需要讀取3個page,用綠色塊表示。


1.3 擷取Reserved Lock

在向資料庫寫資料之前,Sqlite需要擷取一個Reserved Lock,Reserved Lock與Shared Lock類似,同時允許其他事務讀取資料庫。Reserved Lock與Shared Lock相容,但與Reserved Lock互斥,即同一個時刻只允許有一個Reserved Lock。持有Reserved Lock表示事務準備要修改資料檔案了,由於還沒有真正修改檔案,因此允許其他事務繼續進行讀操作,但不允許其他事務進行寫資料庫操作。


1.4 建立記錄檔

在sqlite中,有兩種日誌技術,影子分頁技術和WAL(write ahead log)技術。影子分頁技術是sqlite預設採用的方式,後面的討論都是基於這種假設。在操作資料檔案之前,sqlite首先建立一個記錄檔,並將準備要修改的page的內容寫入日誌,通過這種方式保留了恢複事務的所有原始資訊。無論是資料庫檔案,還是記錄檔,最基本的操作單位都是page。


1.5 修改資料

前面提到,sqlite修改資料前,先將page讀到cache中,因此修改會首先修改cache中的資料。由於每個串連都有自己獨立的page cache,因此寫事務修改自己page cache中的資料,不會影響其他事務,其他事務依然會讀到原始的page資料,不會導致髒讀。下圖中紅色表示修改塊,從圖中可以看到,只有使用者自身 cache的page變成了紅色。

1.6 刷記錄檔

修改完成後,首先將記錄檔寫入磁碟。這個過程非常重要,只有通過刷盤操作(fsync)將日誌持久化,才能在掉電的情況下,通過日誌恢複資料頁。同時,這個動作也非常耗時。


1.7 擷取Exclusive Lock

現在我們需要將之前對page cache的修改寫入資料庫檔案,達到持久化目的。在這個動作之前,首先需要持有Exclusive Lock,擷取該鎖實際包含兩個步驟,首先持有一個Pending Lock,然後再持有Exclusive Lock。Pending Lock允許持有讀鎖事務繼續進行讀操作,但不允許新的讀事務進來。由於新的讀事務被阻止,則將讀事務數目限制在一定的範圍,而已有的讀事務遲早都會執行完,寫事務最終可以擷取到Exclusive Lock,通過這種方式避免寫事務餓死的情況。

1.8 將修改寫入資料檔案並刷盤

一旦持有了Exclusive Lock,則此時sqlite中只有一個事務,沒有其他讀事務去讀檔案。因此,這時候向資料檔案中寫資料是安全的。為了保證寫入動作真正落入磁碟,還需要進行刷盤動作。與刷日誌一樣,將資料檔案修改刷盤動作也是為了保證掉電情況下,更新依然可以持久化,同樣這個操作也很耗時。其中紅色塊表示修改塊,此時使用者進程空間,OS buffer,以及DISK都已經修改。


1.9 刪除記錄檔

進行這步時,記錄檔和資料檔案修改都已經固化到磁碟。如果在1.8步之前,發生掉電,由於記錄檔已經安全落盤,因此可以將資料庫恢複到事務開始前的狀態。由於資料檔案修改已經固化,我們可以將記錄檔刪除。通過記錄檔的存在與否,判斷我們是需要將交易回復還是提交。由於刪檔案也是一個比較耗時的動作,sqlite對此進行了最佳化,通過參數選項,可以選擇將日誌全部初始化0,或是直接將檔案截斷,達到提高效能的目的。


1.10 釋放Exclusive Lock

最後一步是釋放Exclusive Lock,這樣其他事務才有機會讀、寫資料檔案。這裡有一個問題,每個串連都有自己的page cache,如果page cache中的內容已經被改了,並寫入到了檔案中,那麼其它事務如何感知,將自己本地的old-page清理,重新從檔案中讀?sqlite通過一個計數器來控制,這個計數器存在資料庫檔案的第一個page中。每次資料檔案修改時,這個值也會同時自增。事務開始時,會讀取計數器,在讀取page 時,會再次檢查計數器是否發生變化,如果發生變化,說明有事務提交,則將本地的cache全部清空,重新從資料庫檔案中擷取。


2.交易回復

正常情況下,通過上述的事務提交流程,就可以保證事務的ACID特性。但是事務在執行過程中發生異常呢,這時候就需要通過交易回復來將資料庫恢複到事務開始前的狀態。下面假設一種情況,來介紹復原流程。

2.1 發生故障

假設在1.8之前,寫資料庫檔案時,發生了掉電故障。當故障恢複後,情形可能如右圖所示,只有部分頁寫入了磁碟,甚至有一個頁可能唯寫入了一部分。由於執行到這個步驟時,日誌已經安全落盤,因此可以藉助日誌進行恢複。


2.2 熱日誌

任何一個串連在操作資料庫之前,會首先判斷是否有熱日誌存在,因為有熱日誌存在意味著可能需要故障恢複。所謂熱日誌,是指需要事務提交過程中發生了故障,需要利用日誌恢複。


2.3 復原未完成的操作

在利用日誌進行恢複前,首先持有Exclusive Lock,這樣避免多個串連同時進行故障恢複,持有Exclusive Lock後,才可以開始修改資料庫檔案。sqlite從記錄檔中讀取原始的資料頁,然後將資料頁寫回到資料檔案中。由於記錄檔頭部記錄了事務開始時資料檔案的大小,sqlite利用這個資訊來講資料檔案進行截斷到原來的大小,保持檔案大小恢複到事務開始時的水平。


2.4 刪除記錄檔

當所有記錄檔中的資料頁都已經拷貝到資料檔案中後,進行一次刷盤操作,確保修改持久化,這時候記錄檔可以被刪除了。恢複完成後,將Exclusive Lock 降級到Shared Lock。這個過程完成後,資料庫完成恢複。由於整個過程都是sqlite自動完成,使用者完全無感知。對於使用者而言,任何時候使用sqlite操作資料檔案都是安全的,即使在發生了異常的情況下。



sqlite處理事務的一個例子
 
事務在資料庫中是一個重要的概念,使用事務可以保證資料的統一和完整性。同時也可以提高效率。拿我們上面建立的persons表來說,假設我要一次插入20個人的名字才算是操作成功,那麼,在不使用事務的情況下,如果插入過程中出現異常或者在插入過程中出現一些其他資料庫操作的話,就很有可能影響了操作的完整性。所以事務可以很好地解決這樣的情況,首先事務是可以把啟動事務過程中的所有操作視為事務的過程。等到所有過程執行完畢後,我們可以根據操作是否成功來決定事務是否進行提交或者復原。提交事務後會一次性把所有資料提交到資料庫,如果復原了事務就會放棄這次的操作,而對原來表的資料不變更。

那麼,如何啟動,提交還有復原事務呢?SQLite中分別是:BEGIN、COMMIT和ROLLBACK。下面來看一下例子:

 @try{

char *errorMsg;

if (sqlite3_exec(_database, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) {

NSLog(@”啟動事務成功”);

sqlite3_free(errorMsg);

       sqlite3_stmt *statement;

if (sqlite3_prepare_v2(_database, [@"insert into persons(name) values(?);" UTF8String], -1, &statement, NULL)==SQLITE_OK) {

//綁定參數

const char *text=[@”張三” cStringUsingEncoding:NSUTF8StringEncoding];

sqlite3_bind_text(statement, index, text, strlen(text), SQLITE_STATIC);


if (sqlite3_step(statement)!=SQLITE_DONE) {

sqlite3_finalize(statement);

}

}


if (sqlite3_exec(_database, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) {

NSLog(@”提交事務成功”);

}

sqlite3_free(errorMsg);

}else{

sqlite3_free(errorMsg);

}

}

@catch(NSException *e){

char *errorMsg;

if (sqlite3_exec(_database, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) {

NSLog(@”復原事務成功”);

}

sqlite3_free(errorMsg);

}

@finally{

}


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.