今天介紹下Android數據庫鎖機制、性能優化的點,之前去面試的時候面試官問我能同時在同個數據庫里寫兩個記錄么,對兩個不同的表寫數據可以么?我知道肯定是不行的,但說不出個所以然,只能淺顯地說數據庫整體是一個文件,保證并發性的話不能同時對一個文件做寫操作哈。那接下來主要會對SQL鎖機制做個介紹。
- 性能優化
- 鎖機制
1.性能優化
1.1數據庫insert、query、update、delete的API與execSQL、rawQuery函數執行插入、查詢、更新、刪除語句操作花費時間的對比。
(1)批量執行1000條,則SqliteDatabase提供的insert、query、update、delete函數和直接execSQL、rawQuery函數的效率差不多。
(2)批量執行10w條,SqliteDatabase提供的insert、query、update、delete函數相對效率比較低。execSQL省去了拼接sql語句的步驟,當數據庫越大,差別就越大。
1.2批量操作效率
在數據庫中插入大量數據,如果使用遍歷insert操作會導致應用響應緩慢,每一次insert操作都會開啟一個事務,這樣都會執行一次磁盤操作。我們可以通過顯示添加事務來提高效率
db.beginTransaction();
try {
for(int i = 0; i < 10000; i++) {
insert(db, c);
}
db.setTransactionSuccessful();
} catch(Exception ex) {
} finally {
db.endTransaction();
}
這樣會將全部要執行的sql語句先緩存在內存當中,然后等到Commit的時候一次性寫入數據庫,保證數據庫文件只被打開關閉了一次。
2.鎖機制
SQLite是基于鎖來實現并發控制的,SQLite的鎖是粗粒度的,所以是輕量級的,當一個連接要寫數據庫時,所有其他的連接都被鎖住,直到寫連接結束它的事務。
2.1鎖的狀態
SQLite數據庫連接有5種狀態
未加鎖:
未和數據庫建立連接、已建立連接但還沒訪問數據庫、已用BEGIN開始了一個事務但未開始讀寫數據庫,處于這些情形時是未加鎖狀態。
共享鎖:
連接需要從數據庫中讀取數據時,需要申請獲得一個共享鎖,如果獲取成功,則進入共享狀態。
預留鎖:
連接需要寫數據至數據庫時,首先申請一個預留鎖,一個數據庫同時只能有一個預留鎖,預留鎖可以與共享鎖共存。獲得預留鎖后會進入預留狀態,這時會先在緩存區中進行寫操作,操作后的結果依然保存在緩存區中,未真正寫入數據庫。
未決鎖:
連接從預留升級為排它前,需要先升級為未決鎖,這時其他的連接就不能獲取到共享鎖了,但已經擁有共享鎖的連接仍然正常讀數據庫,此時,擁有未決鎖的連接等待其他擁有共享鎖連接完成工作并釋放其共享鎖后,才能提升到排它鎖。
排它鎖:
連接需要提交修改時,需要將預留鎖升級為排它鎖,這時候其他鏈接都無法獲取任何鎖,直到當前連接的排它狀態結束。
一個連接讀數據庫的流程如下:
一個連接寫數據庫的流程如下:
但在實際中可能會出現死鎖的問題,例如在使用事務的情況下。
執行順序1:連接A獲取一個未加鎖
執行順序2:連接B獲取一個未加鎖
執行順序3:連接B要執行寫操作,獲得預留鎖(數據庫連接只能一次有一個預留鎖)
執行順序4:連接A要執行讀操作,獲取共享鎖
執行順序5:連接B要升級為排它鎖前,在執行提交數據庫的操作前,先升級成未決鎖(這時候不能有新的共享鎖了)
執行順序6:連接B要升級為排它鎖,但可能存在4的操作耗時久,共享鎖未被釋放,連接B需等待
執行順序7:連接A要執行寫操作,需要獲取預留鎖
執行順序8:連接A獲取預留鎖失敗,必須先等連接B釋放未決鎖
于是連接A和連接B相互等待對方,發生死鎖。
那么,如何避免這種死鎖情況呢?就要談談事務類型了。
2.2事務類型
DEFERRED
不獲取任何鎖,這種是正常執行流程從上往下的,但對數據庫進行連接時它本身是不會獲取任何鎖的,當對數據庫進行讀操作時,會獲取共享鎖,當對寫操作時,會獲取預留鎖。
IMMEDIATE
在BEGIN時事務會嘗試獲取預留鎖。如果獲取成功,能保證沒有其他可以對數據庫可以進行寫操作,但可以進行讀操作。通俗而言就是當前事務在沒有結束之前,任何其他線程或進程都無法對數據庫進行寫操作。
EXCLUSIVE
事務會嘗試獲取排它鎖。一旦獲取成功,保證沒有額外的連接,這樣就能保證對數據庫進行讀寫操作。通俗而言就是當前事務在沒有結束之前,任何其他線程或進程都無法對數據庫進行讀寫操作。
其實上面死鎖的情況主要是由于一個共享鎖未釋放導致兩個連接互相等待對方。那么如果兩個連接都是以IMMEDIATE的方式開啟事務的話,那么當下只能有一個連接獲取預留鎖,其他的連接就得等它操作完。
可能說的有點懸乎,那么我們看下安卓開啟事務的代碼。
beginTransaction():使用EXCLUSIVE這種方式的。
beginTransactionNonExclusive():使用IMMEDIATE這種方式的。
具體內部邏輯如下:
所以說默認情況下使用事務時,只允許一個連接進行數據庫讀寫,這樣保證了并發性。