SQLite 内存数据库间歇性遇到 SQLITE_LOCKED_SHAREDCACHE

SQLite in-memory database encounters SQLITE_LOCKED_SHAREDCACHE intermittently

我正在使用 mybatis 3.4.6 以及 org.xerial:sqlite-jdbc 3.28.0。下面是我使用启用了共享模式的 内存数据库的配置

db.driver=org.sqlite.JDBC
db.url=jdbc:sqlite:file::memory:?cache=shared

db.url根据这个test class

是正确的

尽管根据我也报告的 issue 有 属性 read_uncommitted 的拼写错误,但我设法使用以下 mybatis 配置设置了正确的事务隔离级别

<environment id="${db.env}">
    <transactionManager type="jdbc"/>
    <dataSource type="POOLED">
        <property name="driver" value="${db.driver}" />
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}" />
        <property name="password" value="${db.password}" />
        <property name="defaultTransactionIsolationLevel" value="1" />
        <property name="driver.synchronous" value="OFF" />
        <property name="driver.transaction_mode" value="IMMEDIATE"/>
        <property name="driver.foreign_keys" value="ON"/>
    </dataSource>
</environment>

这行配置

  <property name="defaultTransactionIsolationLevel" value="1" />

是否设置了 PRAGMA read_uncommitted

的正确值

我很确定,因为我调试了初始化连接并检查值是否设置正确的底层代码

但是在上面的设置下,我的程序在读取时仍然会间歇性地遇到SQLITE_LOCKED_SHAREDCACHE,根据下面屏幕截图中红色矩形突出显示的描述,我认为这不应该发生。我想知道原因以及如何解决,虽然这个错误出现的概率很低。

如有任何想法,我们将不胜感激!!

调试配置如下


===CONFINGURATION==============================================
 jdbcDriver                     org.sqlite.JDBC
 jdbcUrl                        jdbc:sqlite:file::memory:?cache=shared
 jdbcUsername                   
 jdbcPassword                   ************
 poolMaxActiveConnections       10
 poolMaxIdleConnections         5
 poolMaxCheckoutTime            20000
 poolTimeToWait                 20000
 poolPingEnabled                false
 poolPingQuery                  NO PING QUERY SET
 poolPingConnectionsNotUsedFor  0
 ---STATUS-----------------------------------------------------
 activeConnections              5
 idleConnections                5
 requestCount                   27
 averageRequestTime             7941
 averageCheckoutTime            4437
 claimedOverdue                 0
 averageOverdueCheckoutTime     0
 hadToWait                      0
 averageWaitTime                0
 badConnectionCount             0
===============================================================

附件:

例外如下

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.transaction.TransactionException: Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: false.  Cause: org.sqlite.SQLiteException: [SQLITE_LOCKED_SHAREDCACHE]  Contention with a different database connection that shares the cache (database table is locked)
### The error may exist in mapper/MsgRecordDO-sqlmap-mappering.xml
### The error may involve com.super.mock.platform.agent.dal.daointerface.MsgRecordDAO.getRecord
### The error occurred while executing a query
### Cause: org.apache.ibatis.transaction.TransactionException: Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: false.  Cause: org.sqlite.SQLiteException: [SQLITE_LOCKED_SHAREDCACHE]  Contention with a different database connection that shares the cache (database table is locked)

我终于自己解决了这个问题,并在下面分享解决方法,以防其他人将来遇到类似问题。

首先我们可以得到完整的异常调用栈如下图

通过回调指示的源代码,我们有以下发现。

  1. SQLite 是 built-in,默认启用 auto commit,这与默认禁用 auto commit 的 MyBatis 矛盾,因为我们正在使用 SqlSessionManager
  2. MyBatis 将在连接初始化期间使用方法 setDesiredAutoCommit 覆盖自动提交 属性,最终调用 SQLiteConnection#setAutoCommit
  3. SQLiteConnection#setAutoCommit 会对数据库进行 begin immediate 操作,这实际上是独占的,查看下面的源代码截图以获得详细解释,因为我们将事务模式配置为立即

<property name="driver.transaction_mode" value="IMMEDIATE"/>

所以到目前为止,一个明显的解决方案是将事务模式更改为 DEFERRED。另外,MyBatis 和 SQLite 的 auto commit 设置相同的解决方案也被考虑过,但是没有采用,因为在期间无法设置 SQLiteConnection 的自动提交初始化阶段,总会有 switching(从 true 到 false,反之亦然),如果事务模式不是,switch 可能会导致上述错误正确设置