Android封装SQLiteDatabase来管理sqlite数据库,对外提供创建、删除、执行sql语句等通用数据库能力,内部实现有一套连接管理机制,了解其连接管理机制,能够避免潜在的错误,并且有助于数据库问题排查。
SQLiteDatabase通过SqliteSession和SQLiteConnectionPool两个类进行连接管理。
SqliteSession
SqliteSession负责管理数据库连接及事务的生命周期,它是非线程安全的,因此SQLiteDatabase使用ThreadLocal保存各自的SqliteSession对象,模型如下图所示:
申请及释放连接
SqliteSession使用引用计数算法控制连接释放,只有当引用计数为0时,才释放连接。
调用exec执行sql前,通过acquireConnection()方法申请连接,执行结束则调用releaseConnection()方法,如下图:
开启事务前需要先申请连接,成功获取连接后即可开启事务,接着可以调用exec执行sql语句,最后结束事务或回滚,并释放对应的连接。如下图:
由于SqliteSession内部使用引用计数算法控制连接释放,所以在事务中调用exec()并不会释放当前连接,只有在结束事务之后才会将连接释放,因此必须保证事务及时结束,否则容易造成数据库阻塞问题。
SQLiteConnectionPool
结构
SQLiteConnectionPool维护了sqlite的连接池,其内包含两种类型的连接:PrimaryConnect和NonPrimaryConnects,其结构如下图所示:
PrimaryConnect由字段mAvailablePrimaryConnect所持有,它是SQLiteConnection的强引用,打开数据库时该连接就被打开,它存活于整个数据库的生命周期内,只有关闭数据库时才关闭此连接。
NonPrimaryConnects由字段mAvailableNonPrimaryConnects保存,这是一个ArraryList对象,用于保存可用的连接,其容量大小受mMaxConnectionPoolSize大小限制,默认情况下,mMaxConnectionPoolSize大小为1,即意味着默认情况下只存在一个PrimaryConnect。
mConnectionWaterPool和mConnectionWaiterQueue在连接达到上限时时使用,mConnectionWaterPool用于创建Waiter对象,mConnectionWaiterQueue表示正在等待连接的对象。
连接申请及释放
以下是连接池内申请连接及释放的流程图:
申请连接时,优先从mAvailableNonPrimaryConnects中获取,如果没有获取成功,则取mAvailablePrimaryConnect来使用。
若mAvailablePrimaryConnect被其他线程取走,此时先从waiter poll中获取一个waiter对象,再根据其优先级插入mConnectionWaiterQueue中的合适位置,接着阻塞当前线程,等待其他线程释放连接。
当其他线程释放连接时,调用releaseConnect()释放连接,其中会区分连接时PrimaryConnect还是NonPrimaryConnects,归还到对应的地方,接着从mConnectionWaiterQueue取出队首元素,将刚刚释放的连接复制到waiter中,以唤醒之前等待的线程。
注意事项
默认情况下一个连接池内内仅有一个可用的连接对象(mAvailablePrimaryConnect),且SqliteSession在处理事务时只在事务结束时释放连接,如果事务内有耗时操作,就导致此线程长时间霸占连接,其他线程只能等其释放才能正常工作,造成数据库阻塞问题。所以在启用事务的时候要万分小心,避免在期内引入网络请求等非数据库操作。