数据库类的简介

SQLiteOpenHelper:

管理SQLite的帮助类,提供获取SQLiteDatabase实例的方法,

它会在第一次使用数据库时调用获取实例方法时创建SQLiteDatabase实例,

并且处理数据库版本变化,开发人员在实现ContentProvider时都要实现一个自定义的SQLiteOpenHelper类,

处理数据的创建、升级和降级。

SQLiteDatabase:

代表一个打开的SQLite数据库,提供了执行数据库操作的接口方法。如果不需要在进程之间共享数据,

应用程序也可以自行创建这个类的实例来读写SQLite数据库。

SQLiteSession:

SQLiteSession负责管理数据库连接和事务的生命周期,

SQLiteSession通过SQLiteConnectionPool获取数据库连接,从而执行具体的数据库操作。

SQLiteConnectionPool:

数据库连接池,管理所有打开的数据库连接(Connection)。

所有数据库连接都是通过它来打开,打开后会加入连接池,

在读写数据库时需要从连接池中获取一个数据库连接来使用。

SQLiteConnection:

代表了数据库连接,每个Connection封装了一个native层的sqlite3实例,

通过JNI调用SQLite动态库的接口方法操作数据库,Connection要么被Session持有,要么被连接池持有。

CursorFactory:

可选的Cursor工厂,可以提供自定义工厂来创建Cursor。

DatabaseErrorHandler:

可选的数据库异常处理器(目前仅处理数据库Corruption),如果不提供,将会使用默认的异常处理器。

SQLiteDatabaseConfiguration:

数据库配置,应用程序可以创建多个到SQLite数据库的连接,这个类用来保证每个连接的配置都是相同的。

SQLiteQuery和SQLiteStatement:

从抽象类SQLiteProgram派生,封装了SQL语句的执行过程,

在执行时自动组装待执行的SQL语句,并调用SQLiteSession来执行数据库操作。

打开数据库连接

通过 getReadableDatabase getWritableDatabase 方法 在使用的时候,

若未打开数据库,则创建数据库连接,

// SQLiteOpenHelper.java

public SQLiteDatabase getReadableDatabase() {

synchronized (this) {

//参数: writable

return getDatabaseLocked(false); //getWritableDatabase 传true

}

}

private SQLiteDatabase getDatabaseLocked(boolean writable) {

if (mDatabase != null) {

...

//check the invailed

return mDatabase;

}

... Isiniting

SQLiteDatabase db = mDatabase;

try {

try {

...

reopenReadWrite()

...

// transfer the path and params and open the db old code

db = SQLiteDatabase.openDatabase(filePath, params); //old code

// new code in wcdb

if (DEBUG_STRICT_READONLY && !writable) {

final String path = mContext.getDatabasePath(mName).getPath();

db = SQLiteDatabase.openDatabase(path, mPassword, mCipher, mFactory,

SQLiteDatabase.OPEN_READONLY, mErrorHandler, connectionPoolSize);

} else {

mNeedMode = true;

mMode = mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0;

db = Context.openOrCreateDatabase(mContext, mName, mPassword, mCipher,

mMode, mFactory, mErrorHandler, connectionPoolSize);

}

} catch (SQLException ex) {

// open db will try read-only

params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();

// not wcdb

// db = SQLiteDatabase.openDatabase(filePath, params);

db = SQLiteDatabase.openDatabase(path, mPassword, mCipher, mFactory,

SQLiteDatabase.OPEN_READONLY, mErrorHandler);

}

}

...

onConfigure(db);

...

// about the version overwrite the method

onCreate

onUpgrade

}

//SQLiteConnectionPool.java

private SQLiteConnectionPool(SQLiteDatabase db, SQLiteDatabaseConfiguration configuration,

int poolSize) {

mDB = new WeakReference<>(db);

//数据库的配置信息

mConfiguration = new SQLiteDatabaseConfiguration(configuration);

//设置最大的数据库链接个数 //未开启wal的版本poolSize默认为1

setMaxConnectionPoolSizeLocked(poolSize);

}

流程:

SQLiteDatabase ->openOrCreateDatabase

-> openDatabase

->open->openInner

->SQLiteConnectionPool.open

->SQLiteConnectionPool.openConnectionLocked

->SQLiteConnection.open

private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,

boolean primaryConnection) {

//connectionId作为链接id,每次新创建一个数据库链接id自增1

final int connectionId = mNextConnectionId++;

//至此数据库链接被打开 SQLiteConnection.open

//Called by SQLiteConnectionPool only in method SQLiteConnectionPool#openConnectionLocked.

return SQLiteConnection.open(this, configuration,connectionId, primaryConnection); // might throw

}

创建数据库连接场景

创建或打开数据库,调用open

重新加载数据库配置,调用reconfigure(SQLiteDatabaseConfiguration configuration)

创建主链接,调用tryAcquirePrimaryConnectionLocked(int connectionFlags)

创建非主链接,调用tryAcquireNonPrimaryConnectionLocked(String sql, int connectionFlags)

SQLiteConnectionPool.png

数据库连接使用

//SQLiteDatabase.java

public long insert(String table, String nullColumnHack, ContentValues values) {

try {

//1.内部封装SQLiteStatement,2.调用statement.executeInsert();

return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);

} catch (SQLException e) {

return -1;

}

}

//SQLiteStatement.java

public long executeInsert() {

acquireReference();

try {

//通过Session进行数据库操作

return getSession().executeForLastInsertedRowId(

getSql(), getBindArgs(), getConnectionFlags(), null);

} catch (SQLiteDatabaseCorruptException ex) {

checkCorruption(ex);

throw ex;

} finally {

releaseReference();

}

}

// Thread-local for database sessions that belong to this database.

// Each thread has its own database session.

// SQLiteSession的粒度是基于Thread的 每个线程都有一会话,且不可变。

// (Handler内部获取到当前线程的Looper)

private final ThreadLocal mThreadSession = new ThreadLocal() {

@Override

protected SQLiteSession initialValue() {

return createSession();

}

};

SQLiteSession getThreadSession() {

return mThreadSession.get(); // initialValue() throws if database closed

}

SQLiteSession createSession() {

final SQLiteConnectionPool pool;

synchronized (mLock) {

throwIfNotOpenLocked();

pool = mConnectionPoolLocked;

}

return new SQLiteSession(pool);

}

//SQLiteSession.java

/**

* Executes a statement that returns the row id of the last row inserted

* by the statement. Use for INSERT SQL statements.

* @param cancellationSignal A signal to cancel the operation in progress, or null if none.

* @return The row id of the last row that was inserted, or 0 if none.

*/

public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags,

CancellationSignal cancellationSignal) {

//校验sql

if (sql == null) {

throw new IllegalArgumentException("sql must not be null.");

}

//对某些SQL语句(例如“ BEGIN”," COMMIT”和“ ROLLBACK”)执行特殊的重新解释,以确保事务状态不变式为保持。

if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {

return 0;

}

//获取数据库链接

acquireConnection(sql, connectionFlags, false, cancellationSignal); // might throw

try {

//使用链接执行数据库操作

return mConnection.executeForLastInsertedRowId(sql, bindArgs,

cancellationSignal); // might throw

} finally {

//释放数据库链接

releaseConnection(); // might throw

}

}

private void acquireConnection(String sql, int connectionFlags,

CancellationSignal cancellationSignal) {

if (mConnection == null) {

assert mConnectionUseCount == 0;

// 从连接池中获取数据库链接

mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,

cancellationSignal); // might throw

mConnectionFlags = connectionFlags;

}

mConnectionUseCount += 1;

}

流程

SQLiteDatabase#insert

->SQLiteDatabase#insertWithOnConflict

->SQLiteStatement#executeInsert (构建sql语句)

->SQLiteSession#executeForLastInsertedRowId(通过Session进行数据库操作)

->SQLiteSession#acquireConnection

->SQLiteConnectionPool#acquireConnection

总结:

Connection从数据库连接池中获取的

进行数据库操作是通过Session操作Connection

多个线程执行数据库操作会有多个Session(通过ThreadLocal实现)

获取链接

//SQLiteConnectionPool.java

public SQLiteConnection acquireConnection(String sql, int connectionFlags,

CancellationSignal cancellationSignal) {

...

SQLiteConnection connection = waitForConnection(sql, connectionFlags, cancellationSignal);

...

SQLiteTrace callback = mTraceCallback;

if (callback != null) {

long waitTime = SystemClock.uptimeMillis() - startTime;

SQLiteDatabase db = mDB.get();

if (db != null) {

final boolean isPrimary =

(connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;

callback.onConnectionObtained(db, sql, waitTime, isPrimary);

}

}

return connection;

}

private SQLiteConnection waitForConnection(String sql, int connectionFlags,

CancellationSignal cancellationSignal) {

//是否需要主链接

final boolean wantPrimaryConnection =

(connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;

final ConnectionWaiter waiter;

final int nonce;

synchronized (mLock) {

throwIfClosedLocked();

// Abort if canceled.

//如果取消信号的回调不为空,那么执行回调检测是否需要取消

if (cancellationSignal != null) {

cancellationSignal.throwIfCanceled();

}

// Try to acquire a connection.

//尝试获得一个数据库链接

SQLiteConnection connection = null;

//不需要主链接,尝试获取非主链接

if (!wantPrimaryConnection) {

connection = tryAcquireNonPrimaryConnectionLocked(

sql, connectionFlags); // might throw

}

//获取不到非链接,尝试获取主链接

if (connection == null) {

connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw

}

if (connection != null) {

return connection;

}

// No connections available. Enqueue a waiter in priority order.

//没有可用的连接。按优先级进入等待队列排队

final int priority = getPriority(connectionFlags);

final long startTime = SystemClock.uptimeMillis();

//创建一个等待获取链接的对象

waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime,

priority, wantPrimaryConnection, sql, connectionFlags);

ConnectionWaiter predecessor = null;

ConnectionWaiter successor = mConnectionWaiterQueue;

//按照优先级查找插入的位置

while (successor != null) {

if (priority > successor.mPriority) {

waiter.mNext = successor;

break;

}

predecessor = successor;

successor = successor.mNext;

}

//插入等待队列

if (predecessor != null) {

predecessor.mNext = waiter;

} else {

mConnectionWaiterQueue = waiter;

}

nonce = waiter.mNonce;

}

// Set up the cancellation listener.

//设置取消监听器,在等待的过程中如果取消等待那么执行cancelConnectionWaiterLocked

if (cancellationSignal != null) {

cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() {

@Override

public void onCancel() {

synchronized (mLock) {

if (waiter.mNonce == nonce) {

//从等待队列中删除这个节点数据

//给waiter添加OperationCanceledException异常信息

//唤醒waiter对应线程的阻塞

//调用wakeConnectionWaitersLocked判断队列其他waiter是否状态有更新

cancelConnectionWaiterLocked(waiter);

}

}

}

});

}

try {

// Park the thread until a connection is assigned or the pool is closed.

// Rethrow an exception from the wait, if we got one.

//驻留线程,直到分配了连接或关闭了池。

//如果有异常,则从等待中抛出异常。

long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;

long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;

for (;;) {

// Detect and recover from connection leaks.

//是否需要从泄漏中进行恢复,之前被调用onConnectionLeaked //mConnectionLeaked.set(true);

// 获取连接的策略: 定期检查此标志,以便它可以从泄漏的连接中恢复并唤醒

if (mConnectionLeaked.compareAndSet(true, false)) {

synchronized (mLock) {

//为等待数据库链接队列进行链接赋值

wakeConnectionWaitersLocked();

}

}

// how to stop?

// Wait to be unparked (may already have happened), a timeout, or interruption.

//阻塞busyTimeoutMillis毫秒,或者中间被执行LockSupport.unpark (释放许可)

//被执行cancelConnectionWaiterLocked进行取消

//或者被执行wakeConnectionWaitersLocked进行链接分配

LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);

// Clear the interrupted flag, just in case.

Thread.interrupted();//重置当前线程的中断状态

// Check whether we are done waiting yet.

//检查我们是否已经完成等待。

synchronized (mLock) {

throwIfClosedLocked();//如果数据库关闭,那么抛出异常

final SQLiteConnection connection = waiter.mAssignedConnection;

final RuntimeException ex = waiter.mException;

//如果已经分配到链接,或者异常

if (connection != null || ex != null) {

recycleConnectionWaiterLocked(waiter);//回收waiter

if (connection != null) {//返回分配链接

return connection;

}

throw ex; // rethrow!重新抛出异常

}

final long now = SystemClock.uptimeMillis();

if (now < nextBusyTimeoutTime) {

//parkNanos阻塞时间不够busyTimeoutMillis毫秒,被执行LockSupport.unpark

busyTimeoutMillis = now - nextBusyTimeoutTime;

} else {

busyInfo = gatherConnectionPoolBusyInfoLocked();

//重置下次阻塞时间

busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;

nextBusyTimeoutTime = now + busyTimeoutMillis;

}

if (busyInfo != null) {

long waitMillis = now - waiter.mStartTime;

//输出日志

logConnectionPoolBusy(busyInfo, waitMillis, connectionFlags);

SQLiteDatabase db = mDB.get();

SQLiteTrace callback = mTraceCallback;

if (db != null && callback != null) {

// SQLiteTrace 性能监控的回调

callback.onConnectionPoolBusy(db, sql, waitMillis, wantPrimaryConnection,

busyInfo.activeSql, busyInfo.activeTransactions);

}

}

}

}

} finally {

// Remove the cancellation listener.

//有异常,或者获取到分配的链接 那么解绑"取消信号回调"信息

if (cancellationSignal != null) {

cancellationSignal.setOnCancelListener(null);

}

}

}

acquireDBConnection.png

LockSupport.parkNanos 循环判断是否获得了数据库链接,未获取到则继续睡眠,

直到这次操作被取消或者获得数据库链接。

LockSupport.park() 的实现原理是通过二元信号量实现阻塞,

这个信号量最多只能加到1。可以理解为获取释放许可证的场景。

unpark()方法会释放一个许可证,park()方法则是获取许可证,如果当前没有许可证,

则进入休眠状态,知道许可证被释放了才被唤醒。无论执行多少次unpark()方法,也最多只会有一个许可证。

主链接的获取

//SQLiteConnectionPool.java

@GuardedBy("mLock")

private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) {

// If the primary connection is available, acquire it now.

SQLiteConnection connection = mAvailablePrimaryConnection;

if (connection != null) {

mAvailablePrimaryConnection = null;

finishAcquireConnectionLocked(connection, connectionFlags); // might throw

return connection;

}

// Make sure that the primary connection actually exists and has just been acquired.

for (SQLiteConnection acquiredConnection : mAcquiredConnections.keySet()) {

if (acquiredConnection.isPrimaryConnection()) {

return null;

}

}

// Uhoh. No primary connection! Either this is the first time we asked

// for it, or maybe it leaked?

//第一次创建数据库主链接,或者主链接被回收

connection = openConnectionLocked(mConfiguration,

true /*primaryConnection*/); // might throw

finishAcquireConnectionLocked(connection, connectionFlags); // might throw

return connection;

}

总结: 主数据库链接只一个,如果被占用那么需要等待,如果没有那么就需要创建。

获取非主链接

//SQLiteConnectionPool.java

private SQLiteConnection tryAcquireNonPrimaryConnectionLocked(

String sql, int connectionFlags) {

// Try to acquire the next connection in the queue.

SQLiteConnection connection;

//尝试获取队列中的下一个连接。

final int availableCount = mAvailableNonPrimaryConnections.size();

if (availableCount > 1 && sql != null) {

// If we have a choice, then prefer a connection that has the

// prepared statement in its cache.

//检查是否可以在其缓存中选择包含prepare语句的连接。

for (int i = 0; i < availableCount; i++) {

connection = mAvailableNonPrimaryConnections.get(i);

if (connection.isPreparedStatementInCache(sql)) {

// 从非主链接集合中移除

mAvailableNonPrimaryConnections.remove(i);

finishAcquireConnectionLocked(connection, connectionFlags); // might throw

return connection;

}

}

}

if (availableCount > 0) {

// Otherwise, just grab the last one.//next one 抓取非主链接集合中的最后一个

connection = mAvailableNonPrimaryConnections.remove(availableCount - 1);

finishAcquireConnectionLocked(connection, connectionFlags); // might throw

return connection;

}

//如果没有可以的非主链接,则扩展数据库连接池

// Expand the pool if needed.

int openConnections = mAcquiredConnections.size();

if (mAvailablePrimaryConnection != null) {

openConnections += 1;

}

//如果数据库连接池已经达到上限,返回null

if (openConnections >= mMaxConnectionPoolSize) {

return null;

}

//否则创建新的非主链接

connection = openConnectionLocked(mConfiguration,

false /*primaryConnection*/); // might throw

finishAcquireConnectionLocked(connection, connectionFlags); // might throw

return connection;

}

非主数据库链接数量的多少受限于数据库连接池的大小。

数据库链接释放

//SQLiteConnectionPool.java

// Weak references to all acquired connections. The associated value

// indicates whether the connection must be reconfigured before being

// returned to the available connection list or discarded.

// For example, the prepared statement cache size may have changed and

// need to be updated in preparation for the next client.

private final WeakHashMap mAcquiredConnections

= new WeakHashMap<>();

//释放数据库链接,使其返回连接池

public void releaseConnection(SQLiteConnection connection) {

synchronized (mLock) {

//idle事件处理connectionReleased

if (mIdleConnectionHandler != null) {

mIdleConnectionHandler.connectionReleased(connection);

}

//获取这个链接的状态 by invoke recycleConnectionLocked

//NORMAL,正常返回连接池

//RECONFIGURE,必须先重新配置连接,然后才能返回。

//DISCARD,连接必须关闭并丢弃。

AcquiredConnectionStatus status = mAcquiredConnections.remove(connection);

if (status == null) {

throw new IllegalStateException("Cannot perform this operation "

+ "because the specified connection was not acquired "

+ "from this pool or has already been released.");

}

//检测是否已经关闭连接池

if (!mIsOpen) {

closeConnectionAndLogExceptionsLocked(connection);

} else if (connection.isPrimaryConnection()) {//如果是主链接

//判断这个数据库链接是否需要回收

if (recycleConnectionLocked(connection, status)) {

assert mAvailablePrimaryConnection == null;

mAvailablePrimaryConnection = connection;//标识主链接可用,被占用的时候为null

}

//判断队列其他waiter是否状态有更新

wakeConnectionWaitersLocked();

} else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) {

//可用的非主链接数+主链接>=最大链接数的时 关闭这个链接

closeConnectionAndLogExceptionsLocked(connection);

} else {

//判断这个数据库链接是否需要回收 不回收则添加

if (recycleConnectionLocked(connection, status)) {

//将这个链接添加到非主链接容器中

mAvailableNonPrimaryConnections.add(connection);

}

//判断队列其他waiter是否状态有更新

wakeConnectionWaitersLocked();

}

}

}

//唤醒waiter对应的线程 取消已获取许可的park

//将异常赋值给waiter进行抛出

@GuardedBy("mLock")

private void wakeConnectionWaitersLocked() {

// 释放等待

// Unpark all waiters that have requests that we can fulfill.

// This method is designed to not throw runtime exceptions, although we might send

// a waiter an exception for it to rethrow.

ConnectionWaiter predecessor = null;

//链表的head point

ConnectionWaiter waiter = mConnectionWaiterQueue;

boolean primaryConnectionNotAvailable = false;

boolean nonPrimaryConnectionNotAvailable = false;

while (waiter != null) {

boolean unpark = false;

//是否关闭了数据库,如果关闭了那么唤醒所有waiter的线程

if (!mIsOpen) {

unpark = true;

} else {

try {

SQLiteConnection connection = null;

//如果该waiter需要非主链接,而且现在有可用的非主链接

if (!waiter.mWantPrimaryConnection && !nonPrimaryConnectionNotAvailable) {

//获取非主链接

connection = tryAcquireNonPrimaryConnectionLocked(

waiter.mSql, waiter.mConnectionFlags); // might throw

//获取为空,标识现在没有可用的非主链接

if (connection == null) {

nonPrimaryConnectionNotAvailable = true;

}

}

//主链接可以用

if (connection == null && !primaryConnectionNotAvailable) {

//尝试获取主链接

connection = tryAcquirePrimaryConnectionLocked(

waiter.mConnectionFlags); // might throw

//获取为空,标识现在主链接不可用

if (connection == null) {

primaryConnectionNotAvailable = true;

}

}

//获取到了数据库链接

if (connection != null) {

waiter.mAssignedConnection = connection;//改waiter赋值链接

unpark = true;//唤醒该waiter的对应线程

} else if (nonPrimaryConnectionNotAvailable && primaryConnectionNotAvailable) {

// There are no connections available and the pool is still open.

// We cannot fulfill any more connection requests, so stop here.

//连接池仍然可用,但是没有可用的链接,无法对其他的waiter状态进行更新则直接返回

break;

}

} catch (RuntimeException ex) {

// Let the waiter handle the exception from acquiring a connection.

waiter.mException = ex;

unpark = true;

}

}

final ConnectionWaiter successor = waiter.mNext;

//如果需要唤醒,那么从链表中删除这个waiter,并进行对应线程唤醒操作

if (unpark) {

if (predecessor != null) {

predecessor.mNext = successor;

} else {

mConnectionWaiterQueue = successor;

}

waiter.mNext = null;

LockSupport.unpark(waiter.mThread);

} else {

predecessor = waiter;

}

waiter = successor;

}

}

链接的释放有时候是为了回收,有时候为了重用。重用的时候还需要唤醒等待链接队列中获得这个链接的waiter 。

数据库链接池的关闭

数据库的关闭

//SQLiteClosable.java,它是SQLiteDatabase的父类

//释放引用的对象,直到所有的引用都被释放了那么关闭数据库

public void close() {

releaseReference();

}

public void releaseReference() {

boolean refCountIsZero;

synchronized (this) {

refCountIsZero = --mReferenceCount == 0;

}

//引用计数递减

if (refCountIsZero) {

onAllReferencesReleased();

}

}

//SQLiteDatabase.java

@Override

protected void onAllReferencesReleased() {

dispose(false);

}

private void dispose(boolean finalized) {

final SQLiteConnectionPool pool;

synchronized (mLock) {

pool = mConnectionPoolLocked;

//连接池置空,无法进行新操作

mConnectionPoolLocked = null;

}

if (!finalized) {

synchronized (sActiveDatabases) {

//删除当前数据库的引用

sActiveDatabases.remove(this);

}

// 触发连接池的关闭

if (pool != null) {

pool.close(); //SQLiteConnectionPool.close()

}

}

}

数据库连接池的关闭是由数据库关闭触发的。

数据库链接池的关闭

//关闭数据库连接池,停止接受新的数据库链接的请求。

//链接池中的可用链接立即被关闭,其他正在使用的链接被归还到数据的时候关闭

public void close() {

dispose(false);

}

private void dispose(boolean finalized) {

if (!finalized) {

// Close all connections. We don't need (or want) to do this

// when finalized because we don't know what state the connections

// themselves will be in. The finalizer is really just here for CloseGuard.

// The connections will take care of themselves when their own finalizers run.

synchronized (mLock) {

throwIfClosedLocked();//检测是否已经被关闭

mIsOpen = false;//标识数据库连接池关闭

//关闭数据库连接池中目前可用的链接(可用的主链接与非主链接集合)

closeAvailableConnectionsAndLogExceptionsLocked(); //SQLiteConnection.close()

final int pendingCount = mAcquiredConnections.size();

//仍然有链接正在使用中

if (pendingCount != 0) {

Log.i(TAG, "The connection pool for " + mConfiguration.label

+ " has been closed but there are still "

+ pendingCount + " connections in use. They will be closed "

+ "as they are released back to the pool.");

}

//队列其他waiter是否状态有更新

wakeConnectionWaitersLocked();

}

}

}

数据库链接的关闭

//SQLiteConnectionPool.java

// Can't throw.

@GuardedBy("mLock")

private void closeAvailableConnectionsAndLogExceptionsLocked() {

//关闭可用的非主链接

closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();

//关闭主链接

if (mAvailablePrimaryConnection != null) {

closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);

mAvailablePrimaryConnection = null;

}

}

// Can't throw.

@GuardedBy("mLock")

private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {

//循环遍历可用的非主链接,关闭链接

final int count = mAvailableNonPrimaryConnections.size();

for (int i = 0; i < count; i++) {

closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));

}

mAvailableNonPrimaryConnections.clear();

}

// Can't throw.

@GuardedBy("mLock")

private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {

try {

connection.close(); // might throw

if (mIdleConnectionHandler != null) {

mIdleConnectionHandler.connectionClosed(connection);

}

} catch (RuntimeException ex) {

Log.e(TAG, "Failed to close connection, its fate is now in the hands "

+ "of the merciful GC: " + connection, ex);

}

}

数据库关闭的时候引用次数自减,若引用次数归零则真正执行关闭数据库;

数据库关闭清除引用后,进行数据库连接池的关闭;

关闭所有的空闲链接,使用中的连接回归连接池后被关闭;

并发问题的解决

历史实现: SQLite的原子操作实现

默认方法是 rollback journal。

当对 DB 进行写操作的时候,SQLite 首先将准备要修改部分的原始内容(以 Page 为单位)拷贝到“回滚日志”中,用于后续可能的 Rollback操作以及 Crash、断电等意外造成写操作中断时恢复 DB 的原始状态,回滚日志存放于名为“DB文件名-journal”的独立文件中(以下简称“-journal”)。对原始内容做备份后,才能写入修改后的内容到 DB 主文件中,

当写入操作完成,用户提交事务后,SQLite 清空 -journal 的内容,至此完成一个完整的写事务。

rollback_journal.png

Rollback 模式中,每次拷贝原始内容或写入新内容后,都需要确保之前写入的数据真正写入到磁盘,而不是缓存在操作系统中,这需要发起一次 fsync 操作,通知并等待操作系统将缓存真正写入磁盘,这个过程十分耗时。

除了耗时的 fsync 操作,写入 -journal 以及 DB 主文件的时候,是需要独占整个 DB 的,否则别的线程/进程可能读取到写到一半的内容。这样的设计使得写操作与读操作是互斥的,并发性很差。

WAL模式和异步Checkpoint

WCDB 开启了 WAL 模式

针对WAL做了改进,使用异步Checkpoint

WAL 模式+则改变了上述流程,写操作不直接写入 DB 主文件,而是写到“DB文件名-wal”文件的末尾,并且通过 -shm 共享内存文件来实现 -wal 内容的索引。读操作时,将结合 DB 主文件以及 -wal 的内容返回结果。由于读操作只读取 DB 主文件和 -wal 前面非正在写入的部分,不需要读取写操作正在写到一半的内容,WAL 模式下读与写操作的并发由此实现。

WAL 写操作除了上面的流程,还增加了一步:Checkpoint,即将 -wal 的内容与合并到 DB 主文件。 由于写操作将内容临时写到 -wal 文件,-wal 文件会不断增大且拖慢读操作,因此需要定期进行 Checkpoint 操作将 -wal 文件保持在合理的大小。Checkpoint 操作比较耗时且会阻塞读操作,但由于时效性要求较低,遇到堵塞可以暂时放弃Checkpoint 操作. 继续 DB 读写操作,不至于太过影响读写性能。SQLite 官方默认的 Checkpoint 阈值是 1000 page,即当 -wal 文件达到 1000 page 大小时,写操作的线程在完成写操作后,再同步进行 Checkpoint 操作;Android Framework 的 Checkpoint 阈值是 100 page。

wal.png

基于 WAL 的基本工作方式,两个优化点:

写入 -wal 文件时不进行 fsync 操作,因为 -wal 文件损坏只影响新写入的未 Checkpoint 部分数据而非整个数据库损坏,影响相对小

将需要进行fsync的Checkpoint 操作放到独立线程执行,让写操作能尽快返回

异步Checkpoint的基本思路,减少和转移耗时较多而且性能不稳定的 fsync 操作,增强写操作性能和减少突然卡顿的可能性,同时不增加 DB 损坏率。

// SQLiteConnectionPool.java

SQLiteCheckpointListener getCheckpointListener() {

return mCheckpointListener;

}

void setCheckpointListener(SQLiteCheckpointListener listener) {

SQLiteDatabase db = mDB.get();

if (mCheckpointListener != null)

mCheckpointListener.onDetach(db);

mCheckpointListener = listener;

if (mCheckpointListener != null)

mCheckpointListener.onAttach(db);

}

// 调用通过jni

/*package*/ void notifyCheckpoint(String dbName, int pages) {

SQLiteDatabase db = mDB.get();

SQLiteCheckpointListener walHook = mCheckpointListener;

if (walHook == null || db == null)

return;

walHook.onWALCommit(db, dbName, pages);

}

public interface SQLiteCheckpointListener {

void onAttach(SQLiteDatabase db);

//Called immediately when a WAL transaction has been committed

void onWALCommit(SQLiteDatabase db, String dbName, int pages);

void onDetach(SQLiteDatabase db);

}

// 实现类: SQLiteAsyncCheckpointer

public SQLiteAsyncCheckpointer(Looper looper, int threshold, int blockingThreshold) {

mLooper = looper;

mThreshold = threshold;

mBlockingThreshold = blockingThreshold;

mPendingCheckpoints = new HashSet<>();

}

@Override

public void onAttach(SQLiteDatabase db) {

if (mLooper == null) {

mLooper = acquireDefaultLooper();

mUseDefaultLooper = true;

}

mHandler = new Handler(mLooper, this);

mLastSyncMode = db.getSynchronousMode();

db.setSynchronousMode(SQLiteDatabase.SYNCHRONOUS_NORMAL);

}

/**

* shmily add note

* 此处要关注WAL的提交策略

*/

@Override

public void onWALCommit(SQLiteDatabase db, String dbName, int pages) {

if (pages < mThreshold)

return;

boolean blockWriting = pages >= mBlockingThreshold;

Pair p = new Pair<>(db, dbName);

boolean newTask;

synchronized (mPendingCheckpoints) {

newTask = mPendingCheckpoints.add(p);

}

if (!newTask)

return;

/**

* shmily add note

* acquireReference 与 releaseReference 成对调用

* 用于引用计数 引用则次数+1 释放则次数-1

* 在sendMessage之前acquireReference

* handMessage处理完成后释放

*/

db.acquireReference();

Message msg = mHandler.obtainMessage(0, blockWriting ? 1 : 0, 0, p);

mHandler.sendMessage(msg);

}

@Override

public void onDetach(SQLiteDatabase db) {

// 恢复SynchronousMode

db.setSynchronousMode(mLastSyncMode);

mHandler = null;

if (mUseDefaultLooper) {

mLooper = null;

releaseDefaultLooper();

mUseDefaultLooper = false;

}

}

@Override

@SuppressWarnings("unchecked")

public boolean handleMessage(Message msg) {

Pair p = (Pair) msg.obj;

// Pair 的方便之处

SQLiteDatabase db = p.first;

String dbName = p.second;

boolean blockWriting = msg.arg1 != 0;

try {

long time = SystemClock.uptimeMillis();

Pair result = db.walCheckpoint(dbName, blockWriting);

int walPages = result.first;

int checkpointedPages = result.second;

time = SystemClock.uptimeMillis() - time;

onCheckpointResult(db, walPages, checkpointedPages, time);

} finally {

db.releaseReference();

}

synchronized (mPendingCheckpoints) {

if (!mPendingCheckpoints.remove(p))

throw new AssertionError("mPendingCheckpoints.remove(p)");

}

return true;

}

protected void onCheckpointResult(SQLiteDatabase db, int walPages, int checkpointedPages,

long time) {

// implemented by the derived class

}

private static Looper acquireDefaultLooper() {

synchronized (gDefaultThreadLock) {

if (gDefaultThreadRefCount++ == 0) {

// Initialize default handler thread.

if (gDefaultThread != null)

throw new AssertionError("gDefaultThread == null");

gDefaultThread = new HandlerThread("WCDB.AsyncCheckpointer", 4);

gDefaultThread.start();

}

return gDefaultThread.getLooper();

}

}

private static void releaseDefaultLooper() {

synchronized (gDefaultThreadLock) {

if (--gDefaultThreadRefCount <= 0) {

if (gDefaultThreadRefCount < 0)

throw new AssertionError("gDefaultThreadRefCount == 0");

gDefaultThread.quit();

gDefaultThread = null;

}

}

}

mysql连接池源码_WCDB-数据库连接池源码解析相关推荐

  1. mysql配置数据库连接池_三种数据库连接池的配置

    三种数据库连接池的配置及使用(For JDBC) 连接池的优缺点 优点 使用连接池的最主要的优点是性能.创建一个新的数据库连接所耗费的时间主要取决于网络的速 度以及应用程序和数据库服务器的 ( 网络 ...

  2. 连接池及Druid(德鲁伊) 数据库连接池

    连接池及Druid(德鲁伊) 数据库连接池 一.数据库连接池 数据库连接池负责分配.管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个:释放空闲时间超过最大空闲时间 ...

  3. java阿里数据库连接池_Java学习:数据库连接池技术

    本节内容 数据库连接池 Spring JDBC : JDBC Template 数据库连接池 1.概念:其实就是一个容器(集合),存放数据库连接的容器 当系统初始化好后,容器中会申请一些连接对象,当用 ...

  4. javaweb mysql 连接池 c3p0 配置_javaWeb_JDBC_c3p0数据库连接池

    JDBC_c3p0数据库连接池 1.一个基本的c3p0数据库连接池 /** * 一个基本的从池c3p0数据库连接池 */ public static void testC3P0() throws Ex ...

  5. asp.net mysql 连接池_asp.net 使用数据库连接池

    数据库连接数过万的真相,原来我们错怪阿里云RDS了 在一次数据库连接问题中,我们坚持认为数据库连接数过万是阿里云RDS的问题,但后来阿里云提供了当时的数据库连接情况,让我们动摇了自己的想法. 上面这5 ...

  6. mysql连接池的原理_数据库连接池的实现及原理

    对于一个简单的数据库应用,由于对于数据库的访问不是很频繁.这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销.但是对于一个复杂的数据库应用,情况 ...

  7. spring boot socket长连接_springboot 整合阿里 druid 数据库连接池实战

    情景 web 开发中连接数据库基本是必须的,阿里的 druid 是一款非常优秀的开源数据库连接池工具. 本文将介绍一下如何使用 springboot 整合 druid 数据源. 快速开始 maven ...

  8. mysql 常用数据库连接池_常见的数据库连接池

    欢迎进入Java社区论坛,与200万技术人员互动交流 >>进入 2.C3P0 在Hibernate和Spring中默认支持该数据库连接池 需要引入:c3p0-0.9.1.2.jar包,如果 ...

  9. springboot链接MySQL线程池_SpringBoot中的数据库连接池

    内置的连接池 目前Spring Boot中默认支持的连接池有dbcp,dbcp2, tomcat, hikari三种连接池. 数据库连接可以使用DataSource池进行自动配置. 由于Tomcat数 ...

  10. log4j mysql 异步_spring boot:配置druid数据库连接池(开启sql防火墙/使用log4j2做异步日志/spring boot 2.3.2)...

    一,druid数据库连接池的功能? 1,Druid是阿里巴巴开发的号称为监控而生的数据库连接池 它的优点包括: 可以监控数据库访问性能 SQL执行日志 SQL防火墙 2,druid的官方站: http ...

最新文章

  1. 群晖阿里云域名解析_群晖套件系列六:Web station的安装与使用
  2. spring22:Aspectj实现环绕通知@Around
  3. WEBApp-搭建Android开发环境
  4. 仅1年GitHub Star数翻倍,Flink 做了什么?
  5. 用Cocos2dx开发棋牌游戏的观点解析
  6. 客户要求ASP.NET Core API返回特定格式,怎么办?
  7. 特斯拉CEO马斯克:将离开推特一段时间
  8. WCDMA中的基本概念
  9. 常用数据手册(Datasheet)下载网站
  10. 虚拟机VMware插入U盘操作系统崩溃死机的解决方法
  11. 前端模块化发展(CommonJs、AMD、CMD、UMD、ESM)
  12. 教你如何关闭445端口
  13. 微软苏州二期全面封顶,明年投入使用!三期开工时间已定,研发人才将达5000余人!...
  14. stm32 I2C总线入门笔记
  15. # 数值计算:三角形积分
  16. 深度学习框架之Keras入门教程
  17. 设计张程序员专用壁纸
  18. cookie清空Java_java清除及设置cookie代码
  19. 解决忘记rool密码问题
  20. FPGA视觉从入门到放弃——懒人的支持向量机

热门文章

  1. 5款精选阿里云大数据课程,9.9元助你踏出大数据行业入门第一步
  2. “京东钱包” 体验报告
  3. Centos7搭建Ngrok内网穿透
  4. java实现腾讯短信云平台发送短信
  5. MDR外接设备的接线方式
  6. 用vb.net 导出word
  7. 一个游戏设计师需要知道哪些知识?
  8. TensorFlowKeras入门猫狗数据集识别
  9. 文本显示器上位机-界面
  10. 笔记本和台式机之间的谷歌浏览器书签手动同步方法