每次提到连接池我们很快能想到线程池。线程池的创建可以减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
数据库连接是一种关键的有限的昂贵的资源,对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,减少链接不断传销和销毁带来的资源浪费。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
1.最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。
2.最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
3.如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放。
一、Android数据库相关类介绍
SQLiteOpenHelper:管理SQLite的帮助类,提供获取SQLIteDatabase实例的方法,它会在第一次使用数据库时调用获取实例方法时创建SQLiteDatabase实例,并且处理数据库版本变化,开发人员在实现ContentProvider时都要实现一个自定义的SQLiteOpenHelper类,处理数据的创建、升级和降级。
SQLiteDatabase:代表一个打开的SQLite数据库,提供了执行数据库操作的接口方法。如果不需要在进程之间共享数据,应用程序也可以自行创建这个类的实例来读写SQLite数据库。
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来执行数据库操作。这两个类的实现应用了设计模式中的命令模式。
二、SQLiteConnectionPool
数据库连接池我们先看一下它的大小,每个链接的获取以及其他功能?
连接池大小
目前Android系统的实现中,如果以非WAL模式打开数据库,连接池中只会保持一个数据库连接,如果以WAL模式打开数据库,连接池中的最大连接数量则根据系统配置决定,默认配置是两个。
//SQLiteConnectionPool.java
private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
//数据库的配置信息
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
//设置最大的数据库链接个数
setMaxConnectionPoolSizeLocked();
//超时处理句柄设置,如果超时时间为MAX_VALUE,那么链接永远不关闭
// If timeout is set, setup idle connection handler
// In case of MAX_VALUE - idle connections are never closed
if (mConfiguration.idleConnectionTimeoutMs != Long.MAX_VALUE) {
setupIdleConnectionHandler(Looper.getMainLooper(),
mConfiguration.idleConnectionTimeoutMs);
}
}
private void setMaxConnectionPoolSizeLocked() {
if (!mConfiguration.isInMemoryDb()
&& (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0){
//获取debug.sqlite.wal.poolsize配置的大小,默认值是com.android.internal.R.integer.db_connection_pool_size
//最小值为2
mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
} else {
// We don't actually need to always restrict the connection pool size to 1
// for non-WAL databases. There might be reasons to use connection pooling
// with other journal modes. However, we should always keep pool size of 1 for in-memory
// databases since every :memory: db is separate from another.
// For now, enabling connection pooling and using WAL are the same thing in the API.
//内存数据库和非WAL数据库时数据库连接池大小为1
mMaxConnectionPoolSize = 1;
}
}
虽然名为连接池,但是从源码来看,目前实现的池中只有一个数据库连接(以后的Android版本可能会扩展),所以如果应用程序中有大量的并发数据库读和写操作的话,每个操作的时长都可能受到影响,所以数据库操作应放在工作线程中执行,以免影响UI响应。
这里有人可能产生疑问,我在进行Android应用开发的时候是可以并行操作数据库的读写,一个数据库连接能实现并发么?要是一个数据库链接可以实现并发,那么为什么需要数据库连接池?
这里说一下我自己的理解:一个数据库链接是一个Socket 通道,当这个Connection 被其它 Session占用的时候后续的Session 的操作必须等待这个 Connection 被释放,所以数据库的 Connection 的工作其实是串行的,这个在 MySql 和 Oracle 的文档中也能找到描述。所以在Android中默认的数据库连接池只有一个数据库链接的时候,所有在这个数据库上的操作都是串行的。我们平时在多线程中的数据库操作都是串行的。
这些将会下下面代码分析的过程中一一体现出来_
三、数据库链接池的构造
这里主要讲数据库连接池的创建和池中的第一条链接的产生。
//SQLiteConnectionPool.java
public final class SQLiteConnectionPool implements Closeable {
private static final String TAG = "SQLiteConnectionPool";
// Amount of time to wait in milliseconds before unblocking acquireConnection
// and logging a message about the connection pool being busy.
private static final long CONNECTION_POOL_BUSY_MILLIS = 30 * 1000; // 30 seconds
private final CloseGuard mCloseGuard = CloseGuard.get();
private final Object mLock = new Object();
private final AtomicBoolean mConnectionLeaked = new AtomicBoolean();
//数据库的配置信息
private final SQLiteDatabaseConfiguration mConfiguration;
//数据库连接池的最大链接数
private int mMaxConnectionPoolSize;
//数据库是否打开
private boolean mIsOpen;
//创建的链接id
private int mNextConnectionId;
//连接等待池其实是由等待的连接组成的链
private ConnectionWaiter mConnectionWaiterPool;
//连接等待队列
private ConnectionWaiter mConnectionWaiterQueue;
//非主链接的引用,强引用需要主动回收
// Strong references to all available connections.
private final ArrayList<SQLiteConnection> mAvailableNonPrimaryConnections =
new ArrayList<SQLiteConnection>();
//主链接
private SQLiteConnection mAvailablePrimaryConnection;
/***部分代码省略***/
}
四、打开数据库
当我们在进行 SQLiteOpenHelper.getWritableDatabase 和 SQLiteOpenHelper.getReadableDatabase 的时候如果数据库没有打开那么会打开数据库,打开数据库也就是创建数据库链接。
//SQLiteDatabase.java
private static SQLiteDatabase openDatabase(@NonNull String path,
@NonNull OpenParams openParams) {
Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
openParams.mCursorFactory, openParams.mErrorHandler,
openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
//内部调用openInner
db.open();
return db;
}
//打开数据库
private void openInner() {
synchronized (mLock) {
assert mConnectionPoolLocked == null;
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
}
synchronized (sActiveDatabases) {
sActiveDatabases.put(this, null);
}
}
在这里我们看到了SQLiteConnectionPoll 的调用,这里由数据库链接池创建数据库链接从而打开数据库。
//SQLiteConnectionPool.java
public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
//校验数据库信息
if (configuration == null) {
throw new IllegalArgumentException("configuration must not be null.");
}
// Create the pool.创建连接池
SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
pool.open(); // might throw
return pool;
}
// Might throw,打开数据库
private void open() {
// Open the primary connection.
// This might throw if the database is corrupt.
//获取主链接并打开数据库,如果数据库损坏可能抛出异常
mAvailablePrimaryConnection = openConnectionLocked(mConfiguration,
true /*primaryConnection*/); // might throw
// Mark it released so it can be closed after idle timeout
//释放当前链接,以便于被关闭或者被超时回收
synchronized (mLock) {
if (mIdleConnectionHandler != null) {
mIdleConnectionHandler.connectionReleased(mAvailablePrimaryConnection);
}
}
// Mark the pool as being open for business.
mIsOpen = true;
mCloseGuard.open("close");
}
// Might throw.
private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration,
boolean primaryConnection) {
//connectionId作为链接id,每次新创建一个数据库链接id自增1
final int connectionId = mNextConnectionId++;
return SQLiteConnection.open