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 存储系列的其他内容。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,848评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,529评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,824评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,329评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,227评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,879评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,218评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,877评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,159评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,155评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,987评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,736评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,273评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,407评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,663评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,158评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,517评论 2 339

推荐阅读更多精彩内容

  • 对象序列化系列 《Android 对象序列化之你不知道的 Serializable》《Android 对象序列化之...
    godliness阅读 3,062评论 0 3
  • SQLite锁机制 SQLite有一个加锁表,用来帮助不同的写数据库都能够在最后一刻加锁,保证最大的并发性。 SQ...
    carver阅读 2,356评论 0 4
  • 事务定义了一组SQL命令的边界,这组命令或者作为一个整体被全部执行,或者都不执行,这称为数据库完整性的原子性原...
    我系哆啦阅读 1,111评论 0 9
  • SQLiteOpenHelper getReadableDatabase()和getWritableDatabas...
    chandarlee阅读 2,736评论 0 49
  • 二、SQLiteDatabase 做移动应用的人,应该没有人不知道SQLite的吧,但SQLite与其它的关系型数...
    dffd001V阅读 871评论 0 0