Android 存儲優化系列專題
- SharedPreferences 系列
《Android 之不要濫用 SharedPreferences》
《Android 之不要濫用 SharedPreferences(2)— 數據丟失》
- ContentProvider 系列(待更)
《Android 存儲選項之 ContentProvider 啟動過程源碼分析》
《Android 存儲選項之 ContentProvider 深入分析》
- 對象序列化系列
《Android 對象序列化之你不知道的 Serializable》
《Android 對象序列化之 Parcelable 深入分析》
《Android 對象序列化之追求完美的 Serial》
- 數據序列化系列(待更)
《Android 數據序列化之 JSON》
《Android 數據序列化之 Protocol Buffer 使用》
《Android 數據序列化之 Protocol Buffer 源碼分析》
- SQLite 存儲系列
《Android 存儲選項之 SQLiteDatabase 創建過程源碼分析》
《Android 存儲選項之 SQLiteDatabase 源碼分析》
《數據庫連接池 SQLiteConnectionPool 源碼分析》
《SQLiteDatabase 啟用事務源碼分析》
《SQLite 數據庫 WAL 模式工作原理簡介》
《SQLite 數據庫鎖機制與事務簡介》
《SQLite 數據庫優化那些事兒》
在 SQLite 存儲系列的前面幾篇文章中,詳細分析了 Android 提供的數據庫操作框架 SQLiteDatabase,它可以說是整個數據庫框架最核心的類,除了數據庫的基本操作外,Android 也很看重 SQLite 事務管理。SQLiteDatabase 中有很多方法是用來啟動、結束和管理事務的。今天我們就來聊聊 SQLiteDatabase 為事務提供的便利操作。
SQLite 事務簡介
SQLite 有三種不同的事務,使用不同的鎖狀態。事務可以開始于:DEFERRED、IMMEDIATE 或 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 錯誤。
簡單來說,SQLite 為了保證最大化的并發支持,采用的是鎖逐步上升機制,它允許多線程同時讀取數據庫數據,但是寫數據庫依然是互斥的。關于這塊的更多內容可以參考《SQLite 鎖機制與事務簡介》
在前面文章也有簡單提到過系統提供的事務開啟、結束和管理的相關方法。
//啟用事務
void beginTransaction()
void beginTransactionWithListener(SQLiteTransactionListener transactionListener)
//結束事務
void endTransaction()
boolean inTransacetion()
//標志事務是否成功
void setTransactionSuccessful()
beginTransaction() 啟動 SQLite 事務,endTransaction() 結束當前的事務。重要的是,決定事務是否被提交或者回滾取決于事務是否被標注了 “clean”。setTrancactionSuccessful() 函數用來設置這個標志位,這個額外的步驟剛開始讓人反感,但事實上它保證了在事務提交前對所做更改的檢查。如果不存在事務或者事務已經是成功狀態,那么 setTransactionSuccessful() 函數就會拋出 IllegalStateException 異常。
下面我們看下事務相關源碼的具體實現。
- 啟用事務
//開始事務方法一
public void beginTransaction() {
beginTransaction(null /* transactionStatusCallback */, true);
}
//開啟事務方法二
public void beginTransactionNonExclusive() {
beginTransaction(null /* transactionStatusCallback */, false);
}
//開啟事務方法三
public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, true);
}
//開始事務方法四
public void beginTransactionWithListenerNonExclusive(
SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, false);
}
如上,從方法的命名我們也可以看出,方法一和方法三屬于一類,區別在于 SQLiteTransactionListener 監聽事務執行過程。
public interface SQLiteTransactionListener {
/**
* Called immediately after the transaction begins.
*/
void onBegin();
/**
* Called immediately before commiting the transaction.
*/
void onCommit();
/**
* Called if the transaction is about to be rolled back.
*/
void onRollback();
}
然后方法二和方法四是啟用非 EXCLUSIVE 事務,這里實際是 IMMEDIATE 事務類型。方法四同樣可以監聽事務的執行過程。
上面啟用事務最終都會調用下面這個方法:
private void beginTransaction(SQLiteTransactionListener transactionListener,
boolean exclusive) {
acquireReference();
try {
//getThreadSession()返回SQLiteSession
getThreadSession().beginTransaction(
exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
SQLiteSession.TRANSACTION_MODE_IMMEDIATE,
transactionListener,
getThreadDefaultConnectionFlags(false /*readOnly*/), null);
} finally {
releaseReference();
}
}
調用 getThreadSession().beginTransaction(),這里實際調用到 SQLiteSession 的 beginTransaction 方法。關于 SQLiteSession 前面我們有多次分析到,如果還不熟悉可以參考《Android 存儲選項之 SQLiteDatabase 源碼分析》。
需要注意,exclusive 參數在啟用事務方法一和方法三傳遞的都是 true,我們直接跟進 SQLiteSession 的 beginTransaction 方法:
//transactionMode 事務模式:SQLiteSession.TRANSACTION_MODE_EXCLUSIVE/SQLiteSession.TRANSACTION_MODE_IMMEDIATE
public void beginTransaction(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
throwIfTransactionMarkedSuccessful();
beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags,
cancellationSignal);
}
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
//是否取消了本次任務,以拋出異常的方式結束
cancellationSignal.throwIfCanceled();
}
// 事務以棧的方式存儲執行
if (mTransactionStack == null) {
// 當前SQLiteSession首次執行事務,獲取一個SQLiteConnection
acquireConnection(null, connectionFlags, cancellationSignal); // might throw
}
try {
// Set up the transaction such that we can back out safely
// in case we fail part way.
if (mTransactionStack == null) {
// 首次執行事務
// 否則事務將以棧的方式保存
// Execute SQL might throw a runtime exception.
switch (transactionMode) {
case TRANSACTION_MODE_IMMEDIATE:
// IMMEDIATE 事務類型
// 執行SQLiteConnection的execute
mConnection.execute("BEGIN IMMEDIATE;", null,
cancellationSignal); // might throw
break;
case TRANSACTION_MODE_EXCLUSIVE:
// EXCLUSIVE 事務類型
mConnection.execute("BEGIN EXCLUSIVE;", null,
cancellationSignal); // might throw
break;
default:
// 默認延遲事務類型
mConnection.execute("BEGIN;", null, cancellationSignal); // might throw
break;
}
}
// Listener might throw a runtime exception.
if (transactionListener != null) {
try {
//通知監聽者事務開始
transactionListener.onBegin(); // might throw
} catch (RuntimeException ex) {
if (mTransactionStack == null) {
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
throw ex;
}
}
// Bookkeeping can't throw, except an OOM, which is just too bad...
// Transaction通過復用來獲取,
Transaction transaction = obtainTransaction(transactionMode, transactionListener);
// 存在多個事務時,事務之間以棧的方式存儲(鏈表形式)
transaction.mParent = mTransactionStack;
//當前事務的棧底(最后一個事務)
mTransactionStack = transaction;
} finally {
if (mTransactionStack == null) {
releaseConnection(); // might throw
}
}
}
在當前 SQLiteSession 首次執行事務 if (mTransactionStack == null),則會根據參數 transactionMode 開啟相應的事務類型。
事務的最終執行還是交給 SQLiteConnection 中,通過 native 方法調用到 SQLite。然后多個事務之間通過棧的方式存儲(鏈表結構)。看下表示事務 Transaction 對象的創建過程:
//表示事務的Transaction以復用的方式獲取
private Transaction obtainTransaction(int mode, SQLiteTransactionListener listener) {
//復用最后一個recycleTransaction后的Transaction
Transaction transaction = mTransactionPool;
if (transaction != null) {
//當前需要被使用
//將mTransactionPool指向當前Transaction的上一個
mTransactionPool = transaction.mParent;
transaction.mParent = null;
transaction.mMarkedSuccessful = false;
transaction.mChildFailed = false;
} else {
//直接創建
transaction = new Transaction();
}
transaction.mMode = mode;
transaction.mListener = listener;
return transaction;
}
Transaction 類的定義:
//Transaction是一個鏈表結構
private static final class Transaction {
public Transaction mParent;
public int mMode;
public SQLiteTransactionListener mListener;
//當前事務是否執行成功
public boolean mMarkedSuccessful;
//標志上一個事務是否執行成功
public boolean mChildFailed;
}
Transaction 對象的回收過程:
//□ <- □ <- □(這個表示當前回收的)
private void recycleTransaction(Transaction transaction) {
//當前mTransactionPool作為parent
transaction.mParent = mTransactionPool;
transaction.mListener = null;
//將剛被釋放的Transaction再作為當前的mTransactionPool
mTransactionPool = transaction;
}
SQLiteSession 中通過 mTransactionPool 變量實現事務對象的復用機制,Transaction 是一個鏈表結構,內部持有自身類型的 Parent。
//事務復用池
private Transaction mTransactionPool;
//它的存儲結構 □ <- □ <- □(表示 mTransactionPool,最后一個回收的 Transaction)
然后,當前任務的多個事務也是通過鏈表的方式進行保存
//當前任務要執行的事務棧
private Transaction mTransactionStack;
//它的存儲結構 □ <- □ <- □(最后一個事務)
mTransactionStack 表示當前任務的最后一個事務對象。
- 標記當前事務執行成功
public void setTransactionSuccessful() {
//如果沒有開啟過事務拋出異常
throwIfNoTransaction();
//同一個事務不能二次執行
throwIfTransactionMarkedSuccessful();
//標志當前事務執行成功
mTransactionStack.mMarkedSuccessful = true;
}
需要注意,如果我們在當前任務從來未開啟過事務會直接拋出異常:
private void throwIfNoTransaction() {
if (mTransactionStack == null) {
throw new IllegalStateException("Cannot perform this operation because "
+ "there is no current transaction.");
}
}
- 判斷當前任務是否存在事務
public boolean inTransaction() {
acquireReference();
try {
//這里主要就是判斷事務棧是否為null
return getThreadSession().hasTransaction();
} finally {
releaseReference();
}
}
方法 throwIfTransactionMarkedSuccessful 表示同一個事務不能二次通知執行成功。
- 結束一個事務
public void endTransaction() {
acquireReference();
try {
//調用SQLiteSession的endTransaction
getThreadSession().endTransaction(null);
} finally {
releaseReference();
}
}
//實際調用如下
public void endTransaction(CancellationSignal cancellationSignal) {
//如果從未開啟過事務,會拋出異常
throwIfNoTransaction();
assert mConnection != null;
endTransactionUnchecked(cancellationSignal, false);
}
private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
if (cancellationSignal != null) {
//如果取消了則會拋出異常,表示本次執行結束
cancellationSignal.throwIfCanceled();
}
//事務棧的棧頂(實際就是最后一個事務)
final Transaction top = mTransactionStack;
//標志當前事務是否執行成功
//mMarkedSuccessful在setTransactionSuccessful中改變狀態
//mChildFailed是考慮當前執行的事務棧,表示當前事務的子事務是否執行失敗,默認false
//整個事務棧中如果有一個事務執行失敗,會導致整個事務的執行失敗
boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;
RuntimeException listenerException = null;
//當前事務的執行過程監聽器
final SQLiteTransactionListener listener = top.mListener;
if (listener != null) {
try {
if (successful) {
//通知事務將要被提交
listener.onCommit(); // might throw
} else {
//通知事務失敗將要被回滾
listener.onRollback(); // might throw
}
} catch (RuntimeException ex) {
listenerException = ex;
successful = false;
}
}
//當前事務的parent
mTransactionStack = top.mParent;
//回收當前事務
recycleTransaction(top);
//判斷當前要執行的事務棧是否全部執行完成
if (mTransactionStack != null) {
if (!successful) {
//表示當前事務執行失敗
mTransactionStack.mChildFailed = true;
}
} else {
//已經是棧底事務
try {
if (successful) {
//如果成功則提交本次事務
mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
} else {
//否則回滾本次事務
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
} finally {
releaseConnection(); // might throw
}
/**
* 多個事務被保存成棧的方式處理,最后一個開啟的事務自然在棧頂
* 整個事務棧只要有一個事務執行失敗,會導致整個事務的失敗而回滾
* */
}
if (listenerException != null) {
throw listenerException;
}
}
結束事務將會以棧的方式進行處理(這里說的是將最后一個事務表示棧頂事務),在整個任務事務周期中,如果某個事務沒有成功,則會進行標記 mChildFailed = true,直到棧底事務(第一個事務),可以看到只要有一個事務執行失敗最終的計算都會為 false。
//top.mChildFailed標志上一個事務是否執行成功
boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;
此時將會導致整個事務執行回滾。
//已經是棧底事務
try {
if (successful) {
//如果成功則提交本次事務
mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
} else {
//否則回滾本次事務
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
}
小結
SQLiteDatabase 允許在同一個任務中指定多個事務,事務之間以棧的方式存儲和被執行,整個任務中只要有一個事務執行失敗,就會導致本次任務的全部回滾。
不過 SQLiteDatabase 中好像并沒有提供啟用默認事務的方法,事務方法總是以 IMMEDIATE 或 EXCLUSIVE 開始,這主要是為解決潛在可能發生的死鎖問題。
以上便是 Android 系統為支撐 SQLite 事務提供的方法支持,整個過程其實并不難理解,不過要想徹底理解 SQLite 事務管理,還需要結合 SQLite 存儲系列的其它文章。這里也推薦一些進階學習資料。
文中如有不妥或有更好的分析結果,歡迎大家指出;如果對你有幫助還可以繼續閱讀,文章開頭給出的 SQLite 存儲系列的其他內容。