【问题标题】:Flyway stuck when cleaning database (Postgres 11)清理数据库时 Flyway 卡住(Postgres 11)
【发布时间】:2020-10-19 16:22:12
【问题描述】:

我们的应用程序包含不能简单回滚的测试。所以我们决定使用flyway的java API。我们将此方法添加到测试中:

@AfterEach
public void remigrate() {
    flyway.clean();
    flyway.migrate();
}

在日志中我发现只有这个:

2020-06-29 15:20:10.356 DEBUG  [-] 1044 --- [           main] o.f.c.i.s.classpath.ClassPathScanner     : Found resource: db/migration/V0_0_1__database-initialization.sql
2020-06-29 15:20:10.356 DEBUG  [-] 1044 --- [           main] o.f.c.i.s.classpath.ClassPathScanner     : Scanning for classes at classpath:db/migration
2020-06-29 15:20:10.356 DEBUG  [-] 1044 --- [           main] o.f.c.i.c.SqlScriptCallbackFactory       : Scanning for SQL callbacks ...
2020-06-29 15:20:10.356 DEBUG  [-] 1044 --- [           main] o.f.core.internal.util.FeatureDetector   : Spring Jdbc available: true
2020-06-29 15:20:10.361 DEBUG  [-] 1044 --- [           main] o.f.core.internal.command.DbClean        : Cleaning schema "public" ...

此应用程序卡住后。当我尝试选择飞行路线历史表记录时,数据库客户端仅加载。其他表上的选择工作正常,所以我预计 flyway 会在历史表上创建死锁。你知道如何解决它吗?谢谢。

执行器 threaddump 中与 flyway 的 clean 相关的最后一个堆栈跟踪:

{
methodName: "socketRead0",
fileName: "SocketInputStream.java",
lineNumber: -2,
className: "java.net.SocketInputStream",
nativeMethod: true
},
{
methodName: "socketRead",
fileName: "SocketInputStream.java",
lineNumber: 116,
className: "java.net.SocketInputStream",
nativeMethod: false
},
{
methodName: "read",
fileName: "SocketInputStream.java",
lineNumber: 171,
className: "java.net.SocketInputStream",
nativeMethod: false
},
{
methodName: "read",
fileName: "SocketInputStream.java",
lineNumber: 141,
className: "java.net.SocketInputStream",
nativeMethod: false
},
{
methodName: "readMore",
fileName: "VisibleBufferedInputStream.java",
lineNumber: 140,
className: "org.postgresql.core.VisibleBufferedInputStream",
nativeMethod: false
},
{
methodName: "ensureBytes",
fileName: "VisibleBufferedInputStream.java",
lineNumber: 109,
className: "org.postgresql.core.VisibleBufferedInputStream",
nativeMethod: false
},
{
methodName: "read",
fileName: "VisibleBufferedInputStream.java",
lineNumber: 67,
className: "org.postgresql.core.VisibleBufferedInputStream",
nativeMethod: false
},
{
methodName: "receiveChar",
fileName: "PGStream.java",
lineNumber: 321,
className: "org.postgresql.core.PGStream",
nativeMethod: false
},
{
methodName: "processResults",
fileName: "QueryExecutorImpl.java",
lineNumber: 1978,
className: "org.postgresql.core.v3.QueryExecutorImpl",
nativeMethod: false
},
{
methodName: "execute",
fileName: "QueryExecutorImpl.java",
lineNumber: 309,
className: "org.postgresql.core.v3.QueryExecutorImpl",
nativeMethod: false
},
{
methodName: "executeInternal",
fileName: "PgStatement.java",
lineNumber: 446,
className: "org.postgresql.jdbc.PgStatement",
nativeMethod: false
},
{
methodName: "execute",
fileName: "PgStatement.java",
lineNumber: 370,
className: "org.postgresql.jdbc.PgStatement",
nativeMethod: false
},
{
methodName: "executeWithFlags",
fileName: "PgPreparedStatement.java",
lineNumber: 149,
className: "org.postgresql.jdbc.PgPreparedStatement",
nativeMethod: false
},
{
methodName: "execute",
fileName: "PgPreparedStatement.java",
lineNumber: 138,
className: "org.postgresql.jdbc.PgPreparedStatement",
nativeMethod: false
},
{
methodName: "execute",
fileName: "ProxyPreparedStatement.java",
lineNumber: 44,
className: "com.zaxxer.hikari.pool.ProxyPreparedStatement",
nativeMethod: false
},
{
methodName: "execute",
fileName: "HikariProxyPreparedStatement.java",
lineNumber: -1,
className: "com.zaxxer.hikari.pool.HikariProxyPreparedStatement",
nativeMethod: false
},
{
methodName: "invoke",
fileName: null,
lineNumber: -1,
className: "sun.reflect.GeneratedMethodAccessor103",
nativeMethod: false
},
{
methodName: "invoke",
fileName: "DelegatingMethodAccessorImpl.java",
lineNumber: 43,
className: "sun.reflect.DelegatingMethodAccessorImpl",
nativeMethod: false
},
{
methodName: "invoke",
fileName: "Method.java",
lineNumber: 498,
className: "java.lang.reflect.Method",
nativeMethod: false
},
{
methodName: "doExecute",
fileName: "JdbcWrapper.java",
lineNumber: 412,
className: "net.bull.javamelody.JdbcWrapper",
nativeMethod: false
},
{
methodName: "invoke",
fileName: "JdbcWrapper.java",
lineNumber: 137,
className: "net.bull.javamelody.JdbcWrapper$StatementInvocationHandler",
nativeMethod: false
},
{
methodName: "invoke",
fileName: "JdbcWrapper.java",
lineNumber: 294,
className: "net.bull.javamelody.JdbcWrapper$DelegatingInvocationHandler",
nativeMethod: false
},
{
methodName: "execute",
fileName: null,
lineNumber: -1,
className: "com.sun.proxy.$Proxy207",
nativeMethod: false
},
{
methodName: "execute",
fileName: "JdbcTemplate.java",
lineNumber: 215,
className: "org.flywaydb.core.internal.jdbc.JdbcTemplate",
nativeMethod: false
},
{
methodName: "doDrop",
fileName: "PostgreSQLTable.java",
lineNumber: 43,
className: "org.flywaydb.core.internal.database.postgresql.PostgreSQLTable",
nativeMethod: false
},
{
methodName: "drop",
fileName: "SchemaObject.java",
lineNumber: 81,
className: "org.flywaydb.core.internal.database.base.SchemaObject",
nativeMethod: false
},
{
methodName: "doClean",
fileName: "PostgreSQLSchema.java",
lineNumber: 97,
className: "org.flywaydb.core.internal.database.postgresql.PostgreSQLSchema",
nativeMethod: false
},
{
methodName: "clean",
fileName: "Schema.java",
lineNumber: 149,
className: "org.flywaydb.core.internal.database.base.Schema",
nativeMethod: false
},
{
methodName: "call",
fileName: "DbClean.java",
lineNumber: 172,
className: "org.flywaydb.core.internal.command.DbClean$3",
nativeMethod: false
},
{
methodName: "call",
fileName: "DbClean.java",
lineNumber: 169,
className: "org.flywaydb.core.internal.command.DbClean$3",
nativeMethod: false
},
{
methodName: "execute",
fileName: "TransactionTemplate.java",
lineNumber: 74,
className: "org.flywaydb.core.internal.jdbc.TransactionTemplate",
nativeMethod: false
},
{
methodName: "cleanSchema",
fileName: "DbClean.java",
lineNumber: 169,
className: "org.flywaydb.core.internal.command.DbClean",
nativeMethod: false
},
{
methodName: "clean",
fileName: "DbClean.java",
lineNumber: 113,
className: "org.flywaydb.core.internal.command.DbClean",
nativeMethod: false
},
{
methodName: "doClean",
fileName: "Flyway.java",
lineNumber: 1488,
className: "org.flywaydb.core.Flyway",
nativeMethod: false
},
{
methodName: "access$300",
fileName: "Flyway.java",
lineNumber: 85,
className: "org.flywaydb.core.Flyway",
nativeMethod: false
},
{
methodName: "execute",
fileName: "Flyway.java",
lineNumber: 1506,
className: "org.flywaydb.core.Flyway$3",
nativeMethod: false
},
{
methodName: "execute",
fileName: "Flyway.java",
lineNumber: 1499,
className: "org.flywaydb.core.Flyway$3",
nativeMethod: false
},
{
methodName: "execute",
fileName: "Flyway.java",
lineNumber: 1711,
className: "org.flywaydb.core.Flyway",
nativeMethod: false
},
{
methodName: "clean",
fileName: "Flyway.java",
lineNumber: 1499,
className: "org.flywaydb.core.Flyway",
nativeMethod: false
},

【问题讨论】:

  • 您是否尝试过执行线程转储并查看卡在哪里?
  • @MarkBramnik 不,你能告诉我该怎么做吗?
  • 方法有很多。您可以插入 JVisualVM 甚至远程调试器之类的工具并进行线程转储(在 intelliJ 中,它是调试器窗口中照片相机的标志)。如果你无权使用这些工具,你可以使用 spring boot actuator 来生成线程转储(/threaddump端点)
  • @MarkBramnik 我尝试找出线程转储,我发现一些执行flyway clean的线程堆栈跟踪。我将其添加到问题中。
  • "清理架构“公共”..." 这是正确的架构吗? Postgres 文档表明这可能不是正确的模式?见“5.8.2. The Public Schema”:postgresql.org/docs/11/ddl-schemas.html#DDL-SCHEMAS-PUBLIC。检查您的数据库中clean() 之后的public 架构的内容,以确认其未被擦除

标签: spring postgresql spring-boot flyway


【解决方案1】:

好的,现在您知道它在 JDBC 执行期间卡住了。 (我将其添加为答案,因为它太长了无法评论,尽管我承认它可能不能作为一个完整的答案,而是一些旨在帮助您整理这些东西的想法):

无论如何,下一步就是了解查询的具体内容。所以请找出这个并添加到问题中

有很多方法可以实现:

  1. 调试 - 因为线程转储准确地显示了问题所在的行,所以放置一个断点并检查。是的,它有点明显,但仍然在这里列出,因为它可能是最容易做的事情。

  2. log4jdbc之类的东西包装数据源

  3. 在 postgresql 级别:

-- show running queries (pre 9.2)
SELECT procpid, age(clock_timestamp(), query_start), usename, current_query 
FROM pg_stat_activity 
WHERE current_query != '<IDLE>' AND current_query NOT ILIKE '%pg_stat_activity%' 
ORDER BY query_start desc;

-- show running queries (9.2)
SELECT pid, age(clock_timestamp(), query_start), usename, query 
FROM pg_stat_activity 
WHERE query != '<IDLE>' AND query NOT ILIKE '%pg_stat_activity%' 
ORDER BY query_start desc;

为什么要做这一切?因为可能查询生成错误,例如未正确指定模式名称等,值得检查IMO。

我要做的一件重要的事情是确保测试针对没有被其他人使用的专用数据库运行。

所以假设你有一个真正的查询并且看到它很好,我真的会和 DBA 交谈(至少在这里得到 DBA 的建议 - 所以考虑在问题中添加 postgresql 的标签,以便人们熟练掌握数据库可以监控这个问题),因为它不再存在于 java 中。 t 可以是某种锁定(有 ways 可以查看 postgresql 中的锁定,但它是一种高级的东西)。

除此之外,我想提以下几点:

  1. 您曾说过您不能回滚事务,而我认为它是“理所当然”的。但是在 PostgreSQL 中,DDL 也是事务的一部分,那么为什么实际上你不能这样做呢?

  2. 几年前,当我在 Spring Boot 中使用 PostgreSQL 时,我的团队返回而不是采用基于 Testcontainers 的方法。基本上这允许使用“专有”数据库进行测试,此外,由于该解决方案本质上是 dockerized,一旦测试完成,您可以重新启动 docker 容器,这相当于清理数据库。就我而言,这不是必需的,因为正如我上面提到的,我们已经回滚了测试期间所做的所有更改,这足以满足我们的需求。

【讨论】:

  • 对 pg_stat_activity 的选择非常好的建议,我发现在 flayway 的清洁之前有插入选择。在 out 集成测试中,我们使用 spring 存储库保存数据,该存储库将用于测试。这个存储库的插入阻塞了flyway,因为我尝试评论这个保存,flyway 工作。你知道为什么这个插入没有完成吗?
  • 嗯,您可能在这里遇到了锁定问题。插入可能会创建某种类型的锁,因此带有“清理”请求的 flyway 进入此锁并卡住,因为没有人删除此锁。您是否使用 Spring Data 来生成插入?如果有,那么事务管理是怎么工作的(你不是在测试中绕过spring的事务管理吗)?
  • 我发现了问题。在 BeforeEach 方法中,我们为测试插入数据,在 AfterEach 中使用 flyway 清理数据库。所有这些方法显然都在同一个事务中,由于某种原因导致死锁。感谢您对 pg_stat_activity 记录的建议,我发现了它,因此我将其标记为答案。
猜你喜欢
  • 1970-01-01
  • 2015-06-26
  • 2021-09-30
  • 2019-01-16
  • 2017-06-15
  • 1970-01-01
  • 2012-06-05
  • 1970-01-01
  • 2017-02-24
相关资源
最近更新 更多