技术原理Android 高质量开发 — 存储优化

SQLiteDatabase 启用事务源码分析

2019-10-14  本文已影响0人  godliness
闪存
Android 存储优化系列专题

Android 之不要滥用 SharedPreferences
Android 之不要滥用 SharedPreferences(2)— 数据丢失

《Android 存储选项之 ContentProvider 启动过程源码分析》
《Android 存储选项之 ContentProvider 深入分析》

Android 对象序列化之你不知道的 Serializable
Android 对象序列化之 Parcelable 深入分析
Android 对象序列化之追求完美的 Serial

《Android 数据序列化之 JSON》
《Android 数据序列化之 Protocol Buffer 使用》
《Android 数据序列化之 Protocol Buffer 源码分析》

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

上一篇 下一篇

猜你喜欢

热点阅读