Sqlite事物與鎖


1事務

事務定義了一組SQL命令的邊界,這組命令或者作為一個整體被全部執行,或者都不執行。事務的典型實例是轉帳。

2事務的范圍

事務由3個命令控制:BEGIN、COMMIT和ROLLBACK。BEGIN開始一個事務,之后的所有操作都可以取消。COMMIT使BEGIN后的所有命令得到確認;而ROLLBACK還原BEGIN之后的所有操作。如:

   sqlite> BEGIN; 
   sqlite> DELETE FROM foods; 
   sqlite> ROLLBACK; 
   sqlite> SELECT COUNT(*) FROM foods; 

上面開始了一個事務,先刪除了foods表的所有行,但是又用ROLLBACK進行了回卷。再執行SELECT時發現表中沒發生任何改變。
SQLite默認情況下,每條SQL語句自成事務(自動提交模式)。

3沖突解決

違反約束會導致事務的非法結束。大多數數據庫(管理系統)都是簡單地將前面所做的修改全部取消。
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

在android中,可以使用SqliteDatabase的insertWithOnConflict()或updateWithOnConflict()來進行設定。

4數據庫鎖

在SQLite中,鎖和事務是緊密聯系的。為了有效地使用事務,需要了解一些關於如何加鎖的知識。

SQLite采用粗放型的鎖。當一個連接要寫數據庫,所有其它的連接被鎖住,直到寫連接結束了它的事務。SQLite有一個加鎖表,來幫助不同的寫數據庫都能夠在最后一刻再加鎖,以保證最大的並發性。

SQLite使用鎖逐步上升機制,為了寫數據庫,連接需要逐級地獲得排它鎖。SQLite有5個不同的鎖狀態:未加鎖(UNLOCKED)、共享 (SHARED)、保留(RESERVED)、未決(PENDING)和排它(EXCLUSIVE)。每個數據庫連接在同一時刻只能處於其中一個狀態。每 種狀態(未加鎖狀態除外)都有一種鎖與之對應。
最初的狀態是未加鎖狀態,在此狀態下,連接還沒有存取數據庫。當連接到了一個數據庫,甚至已經用BEGIN開始了一個事務時,連接都還處於未加鎖狀態。
未加鎖狀態的下一個狀態是共享狀態。為了能夠從數據庫中讀(不寫)數據,連接必須首先進入共享狀態,也就是說首先要獲得一個共享鎖。多個連接可以 同時獲得並保持共享鎖,也就是說多個連接可以同時從同一個數據庫中讀數據。但哪怕只有一個共享鎖還沒有釋放,也不允許任何連接寫數據庫。
如果一個連接想要寫數據庫,它必須首先獲得一個保留鎖。一個數據庫上同時只能有一個保留鎖。保留鎖可以與共享鎖共存,保留鎖是寫數據庫的第1階段。保留鎖即不阻止其它擁有共享鎖的連接繼續讀數據庫,也不阻止其它連接獲得新的共享鎖。
一旦一個連接獲得了保留鎖,它就可以開始處理數據庫修改操作了,盡管這些修改只能在緩沖區中進行,而不是實際地寫到磁盤。對讀出內容所做的修改保存在內存緩沖區中。

當連接想要提交修改(或事務)時,需要將保留鎖提升為排它鎖。為了得到排它鎖,還必須首先將保留鎖提升為未決鎖。獲得未決鎖之后,其它連接就不能 再獲得新的共享鎖了,但已經擁有共享鎖的連接仍然可以繼續正常讀數據庫。此時,擁有未決鎖的連接等待其它擁有共享鎖的連接完成工作並釋放其共享鎖。
一旦所有其它共享鎖都被釋放,擁有未決鎖的連接就可以將其鎖提升至排它鎖,此時就可以自由地對數據庫進行修改了。所有以前對緩沖區所做的修改都會被寫到數據庫文件。

死鎖
為什么需要了解鎖的機制呢?為了避免死鎖。
考慮下面表4-7所假設的情況。兩個連接——A和B——同時但完全獨立地工作於同一個數據庫。A執行第1條命令,B執行第2、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通過談判解決,因為它們甚至不知道彼此的存在。答案是采用正確的事務類型來完成工作。

5事務的種類

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開始你的事務。

Android中的事物,有2種,一個是IMMEDIATE,一個是EXCLUSIVE,默認SQLiteDatabase的beginTransaction()是EXCLUSIVE事物,beginTransactionNonExclusive()是IMMEDIATE事物

注:本文轉自:http://www.cnblogs.com/frankliiu-java/archive/2010/05/31/1748160.html;添加了部分Android相關解釋


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM