SQLiteDatabase 啟用事務源碼分析

閃存
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 存儲系列的其他內容。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 對象序列化系列 《Android 對象序列化之你不知道的 Serializable》《Android 對象序列化之...
    godliness閱讀 3,177評論 0 3
  • SQLite鎖機制 SQLite有一個加鎖表,用來幫助不同的寫數據庫都能夠在最后一刻加鎖,保證最大的并發性。 SQ...
    carver閱讀 2,412評論 0 4
  • 事務定義了一組SQL命令的邊界,這組命令或者作為一個整體被全部執行,或者都不執行,這稱為數據庫完整性的原子性原...
    我系哆啦閱讀 1,137評論 0 9
  • SQLiteOpenHelper getReadableDatabase()和getWritableDatabas...
    chandarlee閱讀 2,803評論 0 49
  • 二、SQLiteDatabase 做移動應用的人,應該沒有人不知道SQLite的吧,但SQLite與其它的關系型數...
    dffd001V閱讀 899評論 0 0