Android 源码分析

Sqlite 源码分析 -- SQLiteDatabase 使用

2017-12-07  本文已影响0人  _夜

注意事项:

  1. 插入单条数据不需要开启事务;

  2. beginTransaction() 获取 mLock 锁后不会释放,直到调用 endTransaction(). 所以在此期间如果有另一个线程进行 CRUD 操作,获取不到 mLock 锁,只能等待;

  3. 执行 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());
        }
    }
}

上一篇下一篇

猜你喜欢

热点阅读