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_IMMEDIATEpublic 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 存储系列的其它文章。这里也推荐一些进阶学习资料。
- Atomic Commit In SQLite
- SQLite 官方文档
文中如有不妥或有更好的分析结果,欢迎大家指出;如果对你有帮助还可以继续阅读,文章开头给出的 SQLite 存储系列的其他内容。
更多相关文章
- Android(安卓)-- Sqlite事务
- ubuntu android 开发环境配置
- Android(安卓)多线程之synchronized锁住的是代码还是对象(二)
- Android(安卓)面试题总结之Android(安卓)基础(四)
- Android(安卓)AsyncTas开发
- android 去除 除了当前白名单的app
- android:configChanges android:screenOrientation
- onEditorAction不执行
- Android按钮控件之RadioGroup和RadioButton