如何避免Flyway迁移锁定SQLite数据库?

3
我们使用Flyway v3.2.1、SQLite 3.7.13和org.xerial:sqlite-jdbc:3.8.7驱动程序进行数据库迁移。
最大并行数据库连接数似乎非常重要。但是,使用任何连接池大小都会因不同原因而导致迁移失败。
1. 使用大小为2或更多的连接池
存在与多个并行数据库连接相关的锁定问题。 在空的SQLite数据库上进行单个迁移最终会导致:
o.f.core.internal.command.DbMigrate.(:)()  Current version of schema "main": << Empty Schema >>
o.f.core.internal.command.DbMigrate.(:)()  Migrating schema "main" to version 1 - initial
o.f.c.i.u.jdbc.TransactionTemplate.(:)()  Unable to restore autocommit to original value for connection
java.sql.SQLException: database is locked
    at org.sqlite.core.DB.throwex(DB.java:859) ~[sqlite-jdbc-3.8.7.jar:na]
    at org.sqlite.core.DB.exec(DB.java:142) ~[sqlite-jdbc-3.8.7.jar:na]
    at org.sqlite.jdbc3.JDBC3Connection.setAutoCommit(JDBC3Connection.java:152) ~[sqlite-jdbc-3.8.7.jar:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_71]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_71]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_71]
    at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_71]
    at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:126) ~[tomcat-jdbc-7.0.56.jar:na]
    at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109) ~[tomcat-jdbc-7.0.56.jar:na]
    at org.apache.tomcat.jdbc.pool.DisposableConnectionFacade.invoke(DisposableConnectionFacade.java:80) ~[tomcat-jdbc-7.0.56.jar:na]
    at com.sun.proxy.$Proxy76.setAutoCommit(Unknown Source) ~[na:na]
    at org.flywaydb.core.internal.util.jdbc.TransactionTemplate.execute(TransactionTemplate.java:96) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.internal.command.DbMigrate.applyMigration(DbMigrate.java:282) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.internal.command.DbMigrate.access$800(DbMigrate.java:46) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.internal.command.DbMigrate$2.doInTransaction(DbMigrate.java:207) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.internal.command.DbMigrate$2.doInTransaction(DbMigrate.java:156) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.internal.util.jdbc.TransactionTemplate.execute(TransactionTemplate.java:72) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.internal.command.DbMigrate.migrate(DbMigrate.java:156) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.Flyway$1.execute(Flyway.java:1059) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.Flyway$1.execute(Flyway.java:1006) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.Flyway.execute(Flyway.java:1418) [flyway-core-3.2.1.jar:na]
    at org.flywaydb.core.Flyway.migrate(Flyway.java:1006) [flyway-core-3.2.1.jar:na]
    ...

这似乎是一个常见的SQLite陷阱,可以在这里找到描述。

通常情况下,Flyway迁移以及我们剩下的应用程序都遭受频繁的锁定错误:

java.sql.SQLException:[SQLITE_BUSY]数据库文件被锁定

当两个或多个连接处于活动状态时。这就是为什么我们尝试...

2. ...使用大小为1的连接池

我们将并行数据库连接数减少到1(通过使用“maxActive = 1”配置我们的Tomcat JDBC连接池)。

但现在,Flyway失败了。

 ... org.flywaydb.core.api.FlywayException: Unable to obtain Jdbc connection from DataSource
    at org.flywaydb.core.internal.util.jdbc.JdbcUtils.openConnection(JdbcUtils.java:56)
    at org.flywaydb.core.Flyway.execute(Flyway.java:1386)
    at org.flywaydb.core.Flyway.migrate(Flyway.java:1006)
    (...)
Caused by: org.apache.tomcat.jdbc.pool.PoolExhaustedException: [localhost-startStop-1] Timeout: Pool empty. Unable to fetch a connection in 10 seconds, none available[size:1; busy:1; idle:0; lastwait:10000].
    at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:674)
    at org.apache.tomcat.jdbc.pool.ConnectionPool.getConnection(ConnectionPool.java:188)
    at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:128)
    at org.flywaydb.core.internal.util.jdbc.JdbcUtils.openConnection(JdbcUtils.java:50)

为什么?Flyway迁移似乎需要同时使用两个连接。在这里,他们有一个相关的问题,并指出:“看起来Flyway至少使用两个连接:一个用于锁定schema_version表,另一个用于实际运行修改...”


最后,我想知道Flyway如何支持SQLite。两个连接是必需的,但数据库最终被锁定。

然而,SQLite是官方支持的,我认为我搞砸了什么。

Flyway如何与SQLite兼容?


根据:https://github.com/flyway/flyway/blob/master/flyway-core/src/main/java/org/flywaydb/core/internal/dbsupport/sqlite/SQLiteTable.java "无法锁定" + this + "因为SQLite不支持锁定。不支持并发迁移。" - Donald_W
“并发迁移”是指在同一数据库上运行2个或更多应用程序的迁移(请参见https://dev59.com/XF4c5IYBdhLWcg3wc6Dr),但这不是我们在这里使用的情况。我试图进行单个、非并发迁移,但失败了。 - shful
我还注意到Flyway中的sqlite集成测试使用了memfile,因此它可能无法使用实际文件正常工作。 - Donald_W
1
通过测试,使用内存数据库进行迁移仍然卡住了。但这次是表锁而不是整个数据库锁。该测试配置了“共享缓存”,允许SQLite连接共享同一个内存数据库。(然而,在此期间,Axel Fontaine的SingleConnectionDataSource解决方案是正确的。) - shful
2个回答

5

使用类似Spring的SingleConnectionDataSource这样的东西,你就可以了。

更新: Flyway 4.1现在在迁移时自动只使用一个连接,因此不再需要这个了。


1
我有同样的问题,但我不使用Spring。除了什么之外,还可以在SQLite数据库上运行迁移? - ant1g
这种针对SQLite的单连接方法现在已经内置于Flyway 4.1.0及更高版本中,根据此发布说明: SQLite支持现在也基于新的单连接模式,避免了各种潜在的锁定问题 - Basil Bourque

4
抱歉,Flyway不幸使用两个连接到数据库来完成它的事情(一个用于元数据表,一个用于用户对象)。我不确定这是否是必需的,但无论如何,由于它需要两个连接,因此不能被标准大小为1的连接池使用。此外,即使您的池中有2个连接,如果多个线程启动并使用Flyway,则可能会发生死锁。每个线程可能会获得它所需的第一个连接,并无限期地阻塞等待第二个连接。我在我们的产品中实际上遇到了这个问题,我们经常与Flyway交互以检查各个租户的模式版本。这个问题适用于任何池的使用,而不仅仅是SQLite。

发布这篇文章后,我又深入研究了一下他们的源代码,发现他们在4.1版中解决了这个问题。所以如果你遇到了类似的问题,请确保你使用的是4.1及以上版本。 - kylejmcintyre
更新:根据此版本说明,从Flyway 4.1.0开始:SQLite支持现在也基于新的单连接模式,避免了所有可能的锁定问题。 - Basil Bourque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接