提到鎖就不得不說(shuō)到死鎖的問(wèn)題,而SQLite也可能出現(xiàn)死鎖魁淳。
下面舉個(gè)例子:
連接1:BEGIN (UNLOCKED)
連接1:SELECT ... (SHARED)
連接1:INSERT ... (RESERVED)
連接2:BEGIN (UNLOCKED)
連接2:SELECT ... (SHARED)
連接1:COMMIT (PENDING壕探,嘗試獲取EXCLUSIVE鎖冈钦,但還有SHARED鎖未釋放,返回SQLITE_BUSY)
連接2:INSERT ... (嘗試獲取RESERVED鎖浩蓉,但已有PENDING鎖未釋放派继,返回SQLITE_BUSY)
現(xiàn)在2個(gè)連接都在等待對(duì)方釋放鎖,于是就死鎖了捻艳。當(dāng)然,實(shí)際情況并沒(méi)那么糟糕庆猫,任何一方選擇不繼續(xù)等待认轨,回滾事務(wù)就行了。
不過(guò)要更好地解決這個(gè)問(wèn)題月培,就必須更深入地了解事務(wù)了嘁字。
實(shí)際上BEGIN語(yǔ)句可以有3種起始狀態(tài):
DEFERRED:默認(rèn)值,開(kāi)始事務(wù)時(shí)不獲取任何鎖杉畜。進(jìn)行第一次讀操作時(shí)獲取SHARED鎖纪蜒,進(jìn)行第一次寫(xiě)操作時(shí)獲取RESERVED鎖。
IMMEDIATE:開(kāi)始事務(wù)時(shí)獲取RESERVED鎖此叠。
EXCLUSIVE:開(kāi)始事務(wù)時(shí)獲取EXCLUSIVE鎖纯续。
現(xiàn)在考慮2個(gè)事務(wù)在開(kāi)始時(shí)都使用IMMEDIATE方式:
連接1:BEGIN IMMEDIATE (RESERVED)
連接1:SELECT ... (RESERVED)
連接1:INSERT ... (RESERVED)
連接2:BEGIN IMMEDIATE (嘗試獲取RESERVED鎖,但已有RESERVED鎖未釋放,因此事務(wù)開(kāi)始失敗猬错,返回SQLITE_BUSY窗看,等待用戶重試)
連接1:COMMIT (EXCLUSIVE,寫(xiě)入完成后釋放)
連接2:BEGIN IMMEDIATE (RESERVED)
連接2:SELECT ... (RESERVED)
連接2:INSERT ... (RESERVED)
連接2:COMMIT (EXCLUSIVE倦炒,寫(xiě)入完成后釋放)
這樣死鎖就被避免了显沈。
而EXCLUSIVE方式則更為嚴(yán)苛,即使其他連接以DEFERRED方式開(kāi)啟事務(wù)也不會(huì)死鎖:
連接1:BEGIN EXCLUSIVE (EXCLUSIVE)
連接1:SELECT ... (EXCLUSIVE)
連接1:INSERT ... (EXCLUSIVE)
連接2:BEGIN (UNLOCKED)
連接2:SELECT ... (嘗試獲取SHARED鎖逢唤,但已有EXCLUSIVE鎖未釋放拉讯,返回SQLITE_BUSY,等待用戶重試)
連接1:COMMIT (EXCLUSIVE鳖藕,寫(xiě)入完成后釋放)
連接2:SELECT ... (SHARED)
連接2:INSERT ... (RESERVED)
連接2:COMMIT (EXCLUSIVE魔慷,寫(xiě)入完成后釋放)
不過(guò)在并發(fā)很高的情況下,直接獲取EXCLUSIVE鎖的難度比較大吊奢;而且為了避免EXCLUSIVE狀態(tài)長(zhǎng)期阻塞其他請(qǐng)求盖彭,最好的方式還是讓所有寫(xiě)事務(wù)都以IMMEDIATE方式開(kāi)始。
順帶一提页滚,要實(shí)現(xiàn)重試的話召边,可以使用sqlite3_busy_timeout()或sqlite3_busy_handler()函數(shù)笑诅。
由此可見(jiàn)盛正,要想保證線程安全的話,可以有這4種方式:
- SQLite使用單線程模式睬魂,用一個(gè)專門(mén)的線程訪問(wèn)數(shù)據(jù)庫(kù)幻林。
- SQLite使用單線程模式贞盯,用一個(gè)線程隊(duì)列來(lái)訪問(wèn)數(shù)據(jù)庫(kù),隊(duì)列一次只允許一個(gè)線程執(zhí)行沪饺,隊(duì)列里的線程共用一個(gè)數(shù)據(jù)庫(kù)連接躏敢。
- SQLite使用多線程模式,每個(gè)線程創(chuàng)建自己的數(shù)據(jù)庫(kù)連接整葡。
- SQLite使用串行模式件余,所有線程共用全局的數(shù)據(jù)庫(kù)連接。