事務
事務定義了一組SQL命令的邊界,這組命令或者做爲一個總體被所有執行,或者都不執行。事務的典型實例是轉賬。
事務的範圍
事務由3個命令控制:BEGIN、COMMIT和ROLLBACK。BEGIN開始一個事務,以後的全部操做均可以取消。COMMIT使BEGIN後的全部命令獲得確認;而ROLLBACK還原BEGIN以後的全部操做。如:
sqlite> BEGIN;
sqlite> DELETE FROM foods;
sqlite> ROLLBACK;
sqlite> SELECT COUNT(*) FROM foods;
COUNT(*)
412
上面開始了一個事務,先刪除了foods表的全部行,可是又用ROLLBACK進行了回捲。再執行SELECT時發現表中沒發生任何改變。
SQLite默認狀況下,每條SQL語句自成事務(自動提交模式)。
衝突解決
如前所述,違反約束會致使事務的非法結束。大多數數據庫(管理系統)都是簡單地將前面所作的修改所有取消。
SQLite有其獨特的方法來處理約束違反(或說從約束違反中恢復),被稱爲衝突解決。
如:
sqlite> UPDATE foods SET id=800-id;
SQL error: PRIMARY KEY must be unique
SQLite提供5種衝突解決方案:REPLACE、IGNORE、FAIL、ABORT和ROLLBACK。
REPLACE: 當發違反了惟一完整性,SQLite將形成這種違反的記錄刪除,替代以新插入或修改的新記錄,SQL繼續執行,不報錯。
IGNORE
FAIL
ABORT
ROLLBACK
數據庫鎖
在SQLite中,鎖和事務是緊密聯繫的。爲了有效地使用事務,須要瞭解一些關於如何加鎖的知識。
SQLite採用粗放型的鎖。當一個鏈接要寫數據庫,全部其它的鏈接被鎖住,直到寫鏈接結束了它的事務。SQLite有一個加鎖表,來幫助不一樣的寫數據庫都可以在最後一刻再加鎖,以保證最大的併發性。
SQLite使用鎖逐步上升機制,爲了寫數據庫,鏈接須要逐級地得到排它鎖。SQLite有5個不一樣的鎖狀態:未加鎖(UNLOCKED)、共享 (SHARED)、保留(RESERVED)、未決(PENDING)和排它(EXCLUSIVE)。每一個數據庫鏈接在同一時刻只能處於其中一個狀態。每 種狀態(未加鎖狀態除外)都有一種鎖與之對應。
最初的狀態是未加鎖狀態,在此狀態下,鏈接尚未存取數據庫。當鏈接到了一個數據庫,甚至已經用BEGIN開始了一個事務時,鏈接都還處於未加鎖狀態。
未加鎖狀態的下一個狀態是共享狀態。爲了可以從數據庫中讀(不寫)數據,鏈接必須首先進入共享狀態,也就是說首先要得到一個共享鎖。多個鏈接能夠 同時得到並保持共享鎖,也就是說多個鏈接能夠同時從同一個數據庫中讀數據。但哪怕只有一個共享鎖尚未釋放,也不容許任何鏈接寫數據庫。
若是一個鏈接想要寫數據庫,它必須首先得到一個保留鎖。一個數據庫上同時只能有一個保留鎖。保留鎖能夠與共享鎖共存,保留鎖是寫數據庫的第1階段。保留鎖即不阻止其它擁有共享鎖的鏈接繼續讀數據庫,也不阻止其它鏈接得到新的共享鎖。
一旦一個鏈接得到了保留鎖,它就能夠開始處理數據庫修改操做了,儘管這些修改只能在緩衝區中進行,而不是實際地寫到磁盤。對讀出內容所作的修改保存在內存緩衝區中。
當鏈接想要提交修改(或事務)時,須要將保留鎖提高爲排它鎖。爲了獲得排它鎖,還必須首先將保留鎖提高爲未決鎖。得到未決鎖以後,其它鏈接就不能 再得到新的共享鎖了,但已經擁有共享鎖的鏈接仍然能夠繼續正常讀數據庫。此時,擁有未決鎖的鏈接等待其它擁有共享鎖的鏈接完成工做並釋放其共享鎖。
一旦全部其它共享鎖都被釋放,擁有未決鎖的鏈接就能夠將其鎖提高至排它鎖,此時就能夠自由地對數據庫進行修改了。全部之前對緩衝區所作的修改都會被寫到數據庫文件。
死鎖
爲何須要了解鎖的機制呢?爲了不死鎖。
考慮下面表4-7所假設的狀況。兩個鏈接——A和B——同時但徹底獨立地工做於同一個數據庫。A執行第1條命令,B執行第二、3條,等等。
表4-7 一個死鎖的假設狀況
A鏈接 B鏈接
sqlite> BEGIN;
sqlite> BEGIN;
sqlite> INSERT INTO foo VALUES('x');
sqlite> SELECT * FROM foo;
sqlite> COMMIT;
SQL error: database is locked
sqlite> INSERT INTO foo VALUES ('x');
SQL error: database is locked
兩個鏈接都在死鎖中結束。B首先嚐試寫數據庫,也就擁有了一個未決鎖。A再試圖寫,但當其INSERT語句試圖將共享鎖提高爲保留鎖時失敗。
爲了討論的方便,假設鏈接A和B都一直等待數據庫可寫。那麼此時,其它的鏈接甚至都不可以再讀數據庫了,由於B擁有未決鎖(它能阻止其它鏈接得到共享鎖)。那麼時此,不只A和B不能工做,其它全部進程都不能再操做此數據庫了。
若是避免此狀況呢?固然不能讓A和B經過談判解決,由於它們甚至不知道彼此的存在。答案是採用正確的事務類型來完成工做。
事務的種類
SQLite有三種不一樣的事務,使用不一樣的鎖狀態。事務能夠開始於:DEFERRED、MMEDIATE或EXCLUSIVE。事務類型在BEGIN命令中指定:
BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION;
一個DEFERRED事務不獲取任何鎖(直到它須要鎖的時候),BEGIN語句自己也不會作什麼事情——它開始於UNLOCK狀態。默認狀況下就 是這樣的,若是僅僅用BEGIN開始一個事務,那麼事務就是DEFERRED的,同時它不會獲取任何鎖;當對數據庫進行第一次讀操做時,它會獲取 SHARED鎖;一樣,當進行第一次寫操做時,它會獲取RESERVED鎖。
由BEGIN開始的IMMEDIATE事務會嘗試獲取RESERVED鎖。若是成功,BEGIN IMMEDIATE保證沒有別的鏈接能夠寫數據庫。可是,別的鏈接能夠對數據庫進行讀操做;可是,RESERVED鎖會阻止其它鏈接的BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令,當其它鏈接執行上述命令時,會返回SQLITE_BUSY錯誤。這時你就能夠對數據庫進行修改操做了,可是你還不能提交,當你 COMMIT時,會返回SQLITE_BUSY錯誤,這意味着還有其它的讀事務沒有完成,得等它們執行完後才能提交事務。
EXCLUSIVE事務會試着獲取對數據庫的EXCLUSIVE鎖。這與IMMEDIATE相似,可是一旦成功,EXCLUSIVE事務保證沒有其它的鏈接,因此就可對數據庫進行讀寫操做了。
上節那個例子的問題在於兩個鏈接最終都想寫數據庫,可是它們都沒有放棄各自原來的鎖,最終,SHARED鎖致使了問題的出現。若是兩個鏈接都以 BEGIN IMMEDIATE開始事務,那麼死鎖就不會發生。在這種狀況下,在同一時刻只能有一個鏈接進入BEGIN IMMEDIATE,其它的鏈接就得等待。BEGIN IMMEDIATE和BEGIN EXCLUSIVE一般被寫事務使用。就像同步機制同樣,它防止了死鎖的產生。
基本的準則是:若是你正在使用的數據庫沒有其它的鏈接,用BEGIN就足夠了。可是,若是你使用的數據庫有其它的鏈接也會對數據庫進行寫操做,就得使用BEGIN IMMEDIATE或BEGIN EXCLUSIVE開始你的事務