注意事项:
-
插入单条数据不需要开启事务;
-
beginTransaction() 获取 mLock 锁后不会释放,直到调用 endTransaction(). 所以在此期间如果有另一个线程进行 CRUD 操作,获取不到 mLock 锁,只能等待;
-
执行 sql 语句的正确方式:
db.beginTransaction();
try {
...
// 注意该语句放在 try 语句块的最后,表明最终的操作成功
db.setTransactionSuccessful();
} finally {
// 注意该语句放在 finally 语句块中,确定进行 roll back 或 commit
db.endTransaction();
}
一、beginTransaction() 获取 mLock 锁,且不会释放
1. 开启事务
/**
* Begins a transaction in EXCLUSIVE mode.
* <p>
* Transactions can be nested.
* When the outer transaction is ended all of
* the work done in that transaction and all of the nested transactions will be committed or
* rolled back. The changes will be rolled back if any transaction is ended without being
* marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
* </p>
* <p>Here is the standard idiom for transactions:
*
* beginTransaction() 的正确使用方式:
*
* <pre>
* db.beginTransaction();
* try {
* ...
* // 注意该语句放在 try 语句块的最后,表明最终的操作成功
* db.setTransactionSuccessful();
* } finally {
* // 注意该语句放在 finally 语句块中,进行 roll back 或 commit
* db.endTransaction();
* }
* </pre>
*/
public void beginTransaction() {
beginTransaction(null /* transactionStatusCallback */, true);
}
2. 获取 mLock 锁,然后执行 execSQL("BEGIN EXCLUSIVE;");
private void beginTransaction(SQLiteTransactionListener transactionListener, boolean exclusive) {
verifyDbIsOpen();
// 强制获取 mLock (即使是单线程), 当前线程循环等待获取 mLock 锁,直到获取为止
// beginTransaction() 执行成功后不会释放该锁, 锁在 endTransaction() 中释放
lockForced(BEGIN_SQL);
boolean ok = false;
try {
// If this thread already had the lock then get out
if (mLock.getHoldCount() > 1) {
if (mInnerTransactionIsSuccessful) {
// 在 setTransactionSuccessful() 和 endTransaction() 语句中间不允许调用 beginTransaction()
String msg = "Cannot call beginTransaction between " + "calling setTransactionSuccessful and endTransaction";
IllegalStateException e = new IllegalStateException(msg);
Log.e(TAG, "beginTransaction() failed", e);
throw e;
}
ok = true;
// 如果当前线程之前已经持有了 mLock, 则直接返回, (意味着下面的操作已经执行过了,无需再执行)
return;
}
// 该线程第一次持有 mLock (mLock.getHoldCount() == 1)
// This thread didn't already have the lock, so begin a database transaction now.
if (exclusive && mConnectionPool == null) {
// beginTransaction() 走这里
execSQL("BEGIN EXCLUSIVE;");
} else {
execSQL("BEGIN IMMEDIATE;");
}
mTransStartTime = SystemClock.uptimeMillis();
mTransactionListener = transactionListener;
mTransactionIsSuccessful = true;
mInnerTransactionIsSuccessful = false;
if (transactionListener != null) {
try {
transactionListener.onBegin();
} catch (RuntimeException e) {
execSQL("ROLLBACK;");
throw e;
}
}
ok = true;
} finally {
if (!ok) {
// beginTransaction is called before the try block so we must release the lock in the case of failure.
// beginTransaction 成功后不会释放锁, 锁在 endTransaction 中释放
unlockForced();
}
}
}
3. 通过 SQLiteStatement 执行 executeUpdateDelete
public void execSQL(String sql) throws SQLException {
executeSql(sql, null);
}
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
// "BEGIN ..." 不会跳入该逻辑
disableWriteAheadLogging();
mHasAttachedDbs = true;
}
// 通过 SQLiteStatement 执行 executeUpdateDelete
SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
try {
return statement.executeUpdateDelete();
} catch (SQLiteDatabaseCorruptException e) {
onCorruption();
throw e;
} finally {
statement.close();
}
}
4. 调用 native 方法 native_executeSql(mSql)
/**
* 顾名思义,executeUpdateDelete(), SQLiteDatabase 调用 update() 和 delete() 方法时会走这里
* 调用 beginTransaction() 也会走这里
*
* Execute this SQL statement, if the the number of rows affected by execution of this SQL
* statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
*
* @return the number of rows affected by this SQL statement execution.
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public int executeUpdateDelete() {
try {
// 缓存 STATEMENT_UPDATE 和 STATEMENT_BEGIN 类型的 sql 语句
saveSqlAsLastSqlStatement();
// 执行每个 sql 语句前都会执行该方法
// 会导致当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
acquireAndLock(WRITE);
int numChanges = 0;
if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
// beginTransaction() 会走这里
// since the statement doesn't have to be prepared,
// call the following native method which will not prepare
// the query plan
native_executeSql(mSql);
} else {
// update() 和 delete() 会走这里
numChanges = native_execute();
}
return numChanges;
} finally {
// beginTransaction() 不会释放 mLock 锁,, 隐式事务会释放锁
releaseAndUnlock();
}
}
5. 当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁
/**
* 执行每个 sql 语句前都会执行该方法
* 会导致当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
*
* Called before every method in this class before executing a SQL statement,
* this method does the following:
* <ul>
* <li>make sure the database is open</li>
* <li>get a database connection from the connection pool,if possible</li>
* <li>notifies {@link BlockGuard} of read/write</li>
* <li>if the SQL statement is an update, start transaction if not already in one.
* otherwise, get lock on the database</li>
* <li>acquire reference on this object</li>
* <li>and then return the current time _after_ the database lock was acquired</li>
* </ul>
* <p>
* This method removes the duplicate code from the other public
* methods in this class.
*/
private long acquireAndLock(boolean rwFlag) {
mState = 0;
// use pooled database connection handles for SELECT SQL statements
mDatabase.verifyDbIsOpen();
// 获取的 db 仍然是 mDatabase
SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0) ? mDatabase.getDbConnection(mSql) : mDatabase;
// use the database connection obtained above
mOrigDb = mDatabase;
mDatabase = db;
setNativeHandle(mDatabase.mNativeHandle);
if (rwFlag == WRITE) {
BlockGuard.getThreadPolicy().onWriteToDisk();
} else {
BlockGuard.getThreadPolicy().onReadFromDisk();
}
/*
* Special case handling of SQLiteDatabase.execSQL("BEGIN transaction").
* we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held.
* beginTransaction() methods in SQLiteDatabase call lockForced() before
* calling execSQL("BEGIN transaction").
*/
if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) {
if (!mDatabase.isDbLockedByCurrentThread()) {
// transaction is NOT started by calling beginTransaction() methods in
// SQLiteDatabase
mDatabase.setTransactionUsingExecSqlFlag();
}
} else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_UPDATE) {
// got update SQL statement. if there is NO pending transaction, start one
if (!mDatabase.inTransaction()) {
mDatabase.beginTransactionNonExclusive();
mState = TRANS_STARTED;
}
}
// do I have database lock? if not, grab it.
if (!mDatabase.isDbLockedByCurrentThread()) {
// 当前线程循环等待获取 SQLiteDatabase 中的 mLock 锁,直到获取为止
mDatabase.lock(mSql);
mState = LOCK_ACQUIRED;
}
acquireReference();
long startTime = SystemClock.uptimeMillis();
mDatabase.closePendingStatements();
compileAndbindAllArgs();
return startTime;
}
6. beginTransaction() 不会释放 mLock 锁,, 隐式事务会释放锁
/**
* this method releases locks and references acquired in {@link #acquireAndLock(boolean)}
*/
private void releaseAndUnlock() {
releaseReference();
if (mState == TRANS_STARTED) {
try {
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
} else if (mState == LOCK_ACQUIRED) {
// beginTransaction() 不会走这里, 隐式事务会走这里, 即 beginTransaction() 不会释放 mLock 锁
mDatabase.unlock();
}
if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_COMMIT
|| (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_ABORT) {
mDatabase.resetTransactionUsingExecSqlFlag();
}
clearBindings();
// release the compiled sql statement so that the caller's SQLiteStatement no longer
// has a hard reference to a database object that may get deallocated at any point.
release();
// restore the database connection handle to the original value
mDatabase = mOrigDb;
setNativeHandle(mDatabase.mNativeHandle);
}
二、setTransactionSuccessful() 设置操作成功标识
/**
* setTransactionSuccessful() 与 endTransaction() 之间尽量不要做别的事情
*
* Marks the current transaction as successful. Do not do any more database work between
* calling this and calling endTransaction. Do as little non-database work as possible in that
* situation too. If any errors are encountered between this and endTransaction the transaction
* will still be committed.
*
* @throws IllegalStateException if the current thread is not in a transaction or the
* transaction is already marked as successful.
*/
public void setTransactionSuccessful() {
verifyDbIsOpen();
if (!mLock.isHeldByCurrentThread()) {
throw new IllegalStateException("no transaction pending");
}
if (mInnerTransactionIsSuccessful) {
throw new IllegalStateException(
"setTransactionSuccessful may only be called once per call to beginTransaction");
}
mInnerTransactionIsSuccessful = true;
}
三、endTransaction() 进行 COMMIT 或 ROLLBACK,释放 mLock 锁
/**
* End a transaction. See beginTransaction for notes about how to use this and when transactions
* are committed and rolled back.
*/
public void endTransaction() {
// 如 mLock 锁没有被当前线程占据,则抛出异常(从 beginTransaction 开始,mLock 就一直被当前线程占据)
verifyLockOwner();
try {
if (mInnerTransactionIsSuccessful) { // 事务中的操作全部执行成功的情况
mInnerTransactionIsSuccessful = false;
} else {
mTransactionIsSuccessful = false;
}
if (mLock.getHoldCount() != 1) {
return;
}
RuntimeException savedException = null;
if (mTransactionListener != null) {
try {
if (mTransactionIsSuccessful) {
mTransactionListener.onCommit();
} else {
mTransactionListener.onRollback();
}
} catch (RuntimeException e) {
savedException = e;
mTransactionIsSuccessful = false;
}
}
if (mTransactionIsSuccessful) { // beginTransaction() 中被设置为 true
// 执行 "COMMIT;"
execSQL(COMMIT_SQL);
// if write-ahead logging is used, we have to take care of checkpoint.
// TODO: should applications be given the flexibility of choosing when to
// trigger checkpoint?
// for now, do checkpoint after every COMMIT because that is the fastest
// way to guarantee that readers will see latest data.
// but this is the slowest way to run sqlite with in write-ahead logging mode.
if (this.mConnectionPool != null) {
execSQL("PRAGMA wal_checkpoint;");
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
Log.i(TAG, "PRAGMA wal_Checkpoint done");
}
}
// log the transaction time to the Eventlog.
if (ENABLE_DB_SAMPLE) {
logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL);
}
} else {
try {
// 执行 回滚
execSQL("ROLLBACK;");
if (savedException != null) {
throw savedException;
}
} catch (SQLException e) {
if (false) {
Log.d(TAG, "exception during rollback, maybe the DB previously " + "performed an auto-rollback");
}
}
}
} finally {
mTransactionListener = null;
// 释放从 beginTransaction() 就一直占据的 mLock 锁
unlockForced();
if (false) {
Log.v(TAG, "unlocked " + Thread.currentThread() + ", holdCount is " + mLock.getHoldCount());
}
}
}
网友评论