sqlite 檢視、觸發器、索引和事務總結
一 檢視
檢視即虛擬表,它的內容都是派生自其他表的查詢結果,雖然看起來像基本表,但不是基本表,因為檢視的內容是動態生成的。
檢視的用處是將頻繁使用的複雜的查詢放進一個虛擬表,方便查詢。
建立檢視
creat view name as select-stmt;
1). 最簡單的檢視:
sqlite> CREATE VIEW testview AS SELECT * FROM testtable WHERE first_col > 100;
2). 建立臨時檢視: sqlite> CREATE TEMP VIEW tempview AS SELECT * FROM testtable WHERE first_col > 100; 3). "IF NOT EXISTS"從句: sqlite> CREATE VIEW testview AS SELECT * FROM testtable WHERE first_col > 100; Error: table testview already exists sqlite> CREATE VIEW IF NOT EXISTS testview AS SELECT * FROM testtable WHERE first_col > 100;
刪除檢視
drop view name;
sqlite> DROP VIEW testview;
sqlite> DROP VIEW testview;
Error: no such view: testview;
sqlite> DROP VIEW IF EXISTS testview;
建立一個關係複雜的檢視:
creat view details as
select f.name as fd, ft.name as tp, e.name as ep, e.season as ssn
from foods f
inner join food_types ft on f.type_id=ft.id,
inner join foods_episodes fe on f.id=fe.ffod_id,
inner join episodes e on fe.episode_id=e.id;
注意:sqlite目前不支援可更新的檢視,即只允許select操作,insert 和update操作不行
不過可以藉助觸發器實現更新
二 觸發器
當具體的表發生特定的資料庫事件時,觸發器執行對應的SQL指令。觸發器可以用來建立自定義完整性約束、日誌改變、更新表和其他操作。
建立觸發器
creat [temp|temporary] trigger name
[before|after] [insert|update|delete of columns] on table
begin
SQL語句
end;
new old
sqlite提供對錶中已經更新和更新前的行的訪問 。如new.id old.id
衝突解決
replace
ignore
faile
abort
rollback
raise()函式
一個特殊的SQL函式RAISE()可用於觸發器程式,使用如下語法:
raise-function ::= RAISE ( ABORT, error-message ) |
RAISE ( FAIL, error-message ) |
RAISE ( ROLLBACK, error-message ) |
RAISE ( IGNORE )
當觸發器程式執行中呼叫了上述前三個之一的形式時,則執行指定的ON CONFLICT程序(ABORT, FAIL或者ROLLBACK) 且終止當前查詢,返回一個SQLITE_CONSTRAINT錯誤並說明錯誤資訊。
當呼叫RAISE(IGNORE),當前觸發器程式的餘下部分,觸發該觸發器的語句和任何之後的觸發器程式被忽略並且 不恢復對資料庫的已有改變。 若觸發觸發器的語句是一個觸發器程式本身的一部分,則原觸發器程式從下一步起繼續執行。
–建立班級表
create table class
(
id integer primary key autoincrement, –班級編號
className text –班級名稱
);
–建立學生表
create table student
(
id integer primary key autoincrement, –編號
stuName text, –學生名稱
stuSex btext, –性別
stuAge integer , –年齡
classId –班級編號
);
–建立插入觸發器 (建立學生時要觸發插入觸發器去判斷是否存在該班級,存在插入成功,反之插入失敗)
create trigger fk_Insert
before insert on student
for each row
begin
select raise(rollback,’還沒有該班級’)
where (select id from class where id = new.classId ) is null;
end;
–建立更新觸發器 (更新學生時要觸發更新觸發器去判斷是否存在更新班級,存在更新成功,反之更新失敗)
create trigger fk_Update
before update on student
for each row
begin
select raise(rollback,’還沒有該班級’)
where (select id from class where id = new.classId)is null;
end;
–建立刪除觸發器 (刪除班級時,首先根據班級編號刪除該班級學生)
create trigger fk_Delete
before delete on class
for each row
begin
delete from student where classId = old.classId;
end ;
insert into class(className) values(‘s1t64’);
insert into student(stuName,stuSex,stuAge,classId)values(‘zhangsan’,1,23,1);
update student set stuName=’lishi’,classId=1 where id = 1;
select * from class ;
select * from student limit 0,100 ; – 分頁查詢從索引0開始查詢,100條資料
三 索引
索引是一種用來在某種條件下加速查詢的結構。SQLite使用B-樹做索引。索引會增加資料庫的大小,索引使用使用首先要考慮什麼時候使用索引,要不要使用索引。
建立索引:
creat index [unique] index_name on table_name[columns]
可以對欄位進行約束,如collate nocase ,unique等
使用索引:
在單欄位索引的情況下,對於下面的where子句中出現的表示式,SQLite將使用索引:
column {=|<|>|<=|>=} expression
expression {=|<|>|<=|>=} column
column in (expression_list)
column in (subquery) //子查詢
多欄位索引有更復雜的情況
creat table foo (a, b, c, d);
creat index foo_index on foo(a, b, c, d)
foo_index 欄位的順序是從左往右的,在查詢select * from foo where a=1 and b=2 and d=3;只有前兩個條件使用索引,因為沒有有效條件來縮小c到d的d 差距
所以,多欄位索引時,查詢時從左往右使用欄位索引的,直到where子句無法找出有效條件來繼續進行索引。
要使用索引對資料庫的資料操作進行優化,那必須明確幾個問題:
1.什麼是索引
2.索引的原理
3.索引的優缺點
4.什麼時候需要使用索引,如何使用
圍繞這幾個問題,來探究索引在資料庫操作中所起到的作用。
1.資料庫索引簡介
回憶一下小時候查字典的步驟,索引和字典目錄的概念是一致的。字典目錄可以讓我們不用翻整本字典就找到我們需要的內容頁數,然後翻到那一頁就可以。索引也是一樣,索引是對記錄按照多個欄位進行排序的一種展現。對錶中的某個欄位建立索引會建立另一種資料結構,其中儲存著欄位的值,每個值還包括指向與它相關記錄的指標。這樣,就不必要查詢整個資料庫,自然提升了查詢效率。同時,索引的資料結構是經過排序的,因而可以對其執行二分查詢,那就更快了。
2. B-樹與索引
大多數的資料庫都是以B-樹或者B+樹作為儲存結構的,B樹索引也是最常見的索引。先簡單介紹下B-樹,可以增強對索引的理解。
B-樹是為磁碟設計的一種多叉平衡樹,B樹的真正最準確的定義為:一棵含有t(t>=2)個關鍵字的平衡多路查詢樹。一棵M階的B樹滿足以下條件:
1)每個結點至多有M個孩子;
2)除根結點和葉結點外,其它每個結點至少有M/2個孩子;
3)根結點至少有兩個孩子(除非該樹僅包含一個結點);
4)所有葉結點在同一層,葉結點不包含任何關鍵字資訊,可以看作一種外部節點;
5)有K個關鍵字的非葉結點恰好包含K+1個孩子;
B樹中的每個結點根據實際情況可以包含大量的關鍵字資訊和分支(當然是不能超過磁碟塊的大小,根據磁碟驅動(disk drives)的不同,一般塊的大小在1k~4k左右);這樣樹的深度降低了,這就意味著查詢一個元素只要很少結點從外存磁碟中讀入記憶體,很快訪問到要查詢的資料。B-樹上操作的時間通常由存取磁碟的時間和CPU計算時間這兩部分構成。而相對於磁碟的io速度,cpu的計算時間可以忽略不計,所以B樹的意義就顯現出來了,樹的深度降低,而深度決定了io的讀寫次數。
B樹索引是一個典型的樹結構,其包含的元件主要是:
1)葉子節點(Leaf node):包含條目直接指向表裡的資料行。
2)分支節點(Branch node):包含的條目指向索引裡其他的分支節點或者是葉子節點。
3) 根節點(Root node):一個B樹索引只有一個根節點,它實際就是位於樹的最頂端的分支節點。
如下圖所示:
每個索引都包含兩部分內容,一部分是索引本身的值,第二部分即指向資料頁或者另一個索引也的指標。每個節點即為一個索引頁,包含了多個索引。
當你為一個空表建立一個索引,資料庫會分配一個空的索引頁,這個索引頁即代表根節點,在你插入資料之前,這個索引頁都是空的。每當你插入資料,資料庫就會在根節點建立索引條目,。當根節點插滿的時候,再插入資料時,根節點就會分裂。舉個例子,根節點插入瞭如圖所示的資料。(超過4個就分裂),這時候插入H,就會分裂成2個節點,移動G到新的根節點,把H和N放在新的右孩子節點中。如圖所示:
根節點插滿4個節點
插入H,進行分裂。
大致的分裂步驟如下:
1)建立兩個兒子節點
2)將原節點中的資料近似分為兩半,寫入兩個新的孩子節點中。
3)在跟節點中放置指向頁節點的指標
當你不斷向表中插入資料,根節點中指向葉節點的指標也被插滿,當葉子還需要分裂的時候,根節點沒有空間再建立指向新的葉節點的指標。那麼資料庫就會建立分支節點。隨著葉子節點的分裂,根節點中的指標都指向了這些分支節點。隨著資料的不斷插入,索引會增加更多的分支節點,使樹結構變成這樣的一個多級結構。
3. 索引的種類
1)聚集索引:表中行的物理順序與鍵值的邏輯(索引)順序相同。因為資料的物理順序只能有一種,所以一張表只能有一個聚集索引。如果一張表沒有聚集索引,那麼這張表就沒有順序的概念,所有的新行都會插入到表的末尾。對於聚集索引,葉節點即儲存了資料行,不再有單獨的資料頁。就比如說我小時候查字典從來不看目錄,我覺得字典本身就是一個目錄,比如查裴字,只需要翻到p字母開頭的,再按順序找到e。通過這個方法我每次都能最快的查到老師說的那個字,得到老師的表揚。
2)非聚集索引:表中行的物理順序與索引順序無關。對於非聚集索引,葉節點儲存了索引欄位值以及指向相應資料頁的指標。葉節點緊鄰在資料之上,對資料頁的每一行都有相應的索引行與之對應。有時候查字典,我並不知道這個字讀什麼,那我就不得不通過字典目錄的“部首”來查找了。這時候我會發現,目錄中的排序和實際正文的排序是不一樣的,這對我來說很苦惱,因為我不能比別人快了,我需要先再目錄中找到這個字,再根據頁數去找到正文中的字。
4.索引與資料的查詢,插入與刪除
1)查詢。查詢操作就和查字典是一樣的。當我們去查詢指定記錄時,資料庫會先查詢根節點,將待查資料與根節點的資料進行比較,再通過根節點的指標查詢下一個記錄,直到找到這個記錄。這是一個簡單的平衡樹的二分搜尋的過程,我就不贅述了。在聚集索引中,找到頁節點即找到了資料行,而在非聚集索引中,我們還需要再去讀取資料頁。
2)插入。聚集索引的插入操作比較複雜,最簡單的情況,插入操作會找到對於的資料頁,然後為新資料騰出空間,執行插入操作。如果該資料頁已經沒有空間,那就需要拆分資料頁,這是一個非常耗費資源的操作。對於僅有非聚集索引的表,插入只需在表的末尾插入即可。如果也包含了聚集索引,那麼也會執行聚集索引需要的插入操作。
3)刪除。刪除行後下方的資料會向上移動以填補空缺。如果刪除的資料是該資料頁的最後一行,那麼這個資料頁會被回收,它的前後一頁的指標會被改變,被回收的資料頁也會在特定的情況被重新使用。與此同時,對於聚集索引,如果索引頁只剩一條記錄,那麼該記錄可能會移動到鄰近的索引表中,原來的索引頁也會被回收。而非聚集索引沒辦法做到這一點,這就會導致出現多個數據頁都只有少量資料的情況。
5. 索引的優缺點
其實通過前面的介紹,索引的優缺點已經一目瞭然。
先說優點:
1)大大加快資料的檢索速度,這也是建立索引的最主要的原因
2)加速表和表之間的連線,特別是在實現資料的參考完整性方面特別有意義。
3)在使用分組和排序子句進行資料檢索時,同樣可以顯著減少查詢中分組和排序的時間。
再說缺點:
1)建立索引需要耗費一定的時間,但是問題不大,一般索引只要build一次
2)索引需要佔用物理空間,特別是聚集索引,需要較大的空間
3)當對錶中的資料進行增加、刪除和修改的時候,索引也要動態的維護,降低了資料的維護速度,這個是比較大的問題。
6.索引的使用
根據上文的分析,我們大致對什麼時候使用索引有了自己的想法(如果你沒有,回頭再看一遍。。。)。一般我們需要在這些列上建立索引:
1)在經常需要搜尋的列上,這是毋庸置疑的;
2)經常同時對多列進行查詢,且每列都含有重複值可以建立組合索引,組合索引儘量要使常用查詢形成索引覆蓋(查詢中包含的所需欄位皆包含於一個索引中,我們只需要搜尋索引頁即可完成查詢)。 同時,該組合索引的前導列一定要是使用最頻繁的列。對於前導列的問題,在後面sqlite的索引使用介紹中還會做討論。
3)在經常用在連線的列上,這些列主要是一些外來鍵,可以加快連線的速度,連線條件要充分考慮帶有索引的表。;
4)在經常需要對範圍進行搜尋的列上建立索引,因為索引已經排序,其指定的範圍是連續的,同樣,在經常需要排序的列上最好也建立索引。
6)在經常放到where子句中的列上面建立索引,加快條件的判斷速度。要注意的是where字句中對列的任何操作(如計算表示式,函式)都需要對錶進行整表搜尋,而沒有使用該列的索引。所以查詢時儘量把操作移到等號右邊。
對於以下的列我們不應該建立索引:
1)很少在查詢中使用的列
2)含有很少非重複資料值的列,比如只有0,1,這時候掃描整表通常會更有效
3)對於定義為TEXT,IMAGE的資料不應該建立索引。這些欄位長度不固定,或許很長,或許為空。
當然,對於更新操作遠大於查詢操作時,不建立索引。也可以考慮在大規模的更新操作前drop索引,之後重新建立,不過這就需要把建立索引對資源的消耗考慮在內。總之,使用索引需要平衡投入與產出,找到一個產出最好的點。
- 在sqlite中使用索引
1)Sqlite不支援聚集索引,Android預設需要一個_id欄位,這保證了你插入的資料會按“_id”的整數順序插入,這個integer型別的主鍵就會扮演和聚集索引一樣的角色。所以不要再在對於宣告為:INTEGER PRIMARY KEY的主鍵上建立索引。
2)很多對索引不熟悉的朋友在表中建立了索引,卻發現沒有生效,其實這大多數和我接下來講的有關。對於where子句中出現的列要想索引生效,會有一些限制,這就和前導列有關。所謂前導列,就是在建立複合索引語句的第一列或者連續的多列。比如通過:CREATE INDEX comp_ind ON table1(x, y, z)建立索引,那麼x,xy,xyz都是前導列,而yz,y,z這樣的就不是。下面講的這些,對於其他資料庫或許會有一些小的差別,這裡以sqlite為標準。在where子句中,前導列必須使用等於或者in操作,最右邊的列可以使用不等式,這樣索引才可以完全生效。同時,where子句中的列不需要全建立了索引,但是必須保證建立索引的列之間沒有間隙。舉幾個例子來看吧:
用如下語句建立索引:
CREATE INDEX idx_ex1 ON ex1(a,b,c,d,e,…,y,z);
這裡是一個查詢語句:
…WHERE a=5 AND b IN (1,2,3) AND c IS NULL AND d=’hello’
這顯然對於abcd四列都是有效的,因為只有等於和in操作,並且是前導列。
再看一個查詢語句:
… WHERE a=5 AND b IN (1,2,3) AND c>12 AND d=’hello’
那這裡只有a,b和c的索引會是有效的,d列的索引會失效,因為它在c列的右邊,而c列使用了不等式,根據使用不等式的限制,c列已經屬於最右邊。
最後再看一條:
… WHERE b IN (1,2,3) AND c NOT NULL AND d=’hello’
索引將不會被使用,因為沒有使用前導列,這個查詢會是一個全表查詢。
3)對於between,or,like,都無法使用索引。
如 …WHERE myfield BETWEEN 10 and 20;
這時就應該將其轉換成:
…WHERE myfield >= 10 AND myfield <= 20;
再如LIKE:…mytable WHERE myfield LIKE ‘sql%’;;
此時應該將它轉換成:
…WHERE myfield >= ‘sql’ AND myfield < ‘sqm’;
再如OR:…WHERE myfield = ‘abc’ OR myfield = ‘xyz’;
此時應該將它轉換成:
…WHERE myfield IN (‘abc’, ‘xyz’);
其實除了索引,對查詢效能的影響因素還有很多,比如表的連線,是否排序等。影響資料庫操作的整體效能就需要考慮更多因素,使用更對的技巧,不得不說這是一個很大的學問。
最後在android上使用sqlite寫一個簡單的例子,看下索引對資料庫操作的影響。
建立如下表和索引:
db.execSQL(“create table if not exists t1(a,b)”);
db.execSQL(“create index if not exists ia on t1(a,b)”);
插入10萬條資料,分別對錶進行如下操作:
select * from t1 where a=’90012’
插入:insert into t1(a,b) values(‘10008’,’name1.6982235534984673’)
更新:update t1 set b=’name1.999999’ where a = ‘887’
刪除:delete from t1 where a = ‘1010’
資料如下(5次不同的操作取平均值):
操作 無索引 有索引
查詢 170ms 5ms
插入 65ms 75ms
更新 240ms 52ms
刪除 234ms 78ms
可以看到顯著提升了查詢的速度,稍稍減慢了插入速度,還稍稍提升了更新資料和刪除資料的速度。如果把更新和刪除中的where子句中的列換成b,速度就和沒有索引一樣了,因為索引失效。所以索引能大幅度提升查詢速度,對於刪除和更新操作,如果where子句中的列使用了索引,即使需要重新build索引,有可能速度還是比不使用索引要快的。對與插入操作,索引顯然是個負擔。同時,索引讓db的大小增加了2倍多。
還有個要吐槽的是,android中的rawQurey方法,執行完sql語句後返回一個cursor,其實並沒有完成一個查詢操作,我在rawquery之前和之後計算查詢時間,永遠是1ms…這讓我無比苦悶。看了下原始碼,在對cursor呼叫moveToNext這些移動遊標方法時,都會最終先呼叫getCount方法,而getCount方法才會呼叫native方法呼叫真正的查詢操作。這種設計顯然更加合理。
四 事務和鎖
sqlite的事務和鎖
事務
事務定義了一組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執行第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通過談判解決,因為它們甚至不知道彼此的存在。答案是採用正確的事務型別來完成工作。
事務的種類
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開始你的事務。