为了这个,我已经把头发扯了几天了。一段时间以来,我们一直在遇到一些排他性数据库锁问题,从而导致我们的生产系统出现性能问题。我能够仔细查看它,并注意到持有排他锁的查询是由 Hibernate 的延迟加载生成的选择。
我们使用 Spring 的事务管理,@Transactional( readOnly= "true")
是在服务入口点定义的。我们使用每请求会话模型和映射到传输对象的实体。数据库默认隔离级别是读提交。JDBC 驱动程序配置为已提交读。我使用以下方法检查了实际事务的隔离级别:
select current_setting('transaction_isolation')
哪个返回已提交的读取。我们使用 JPA 来配置 Hibernate 映射。我们没有在任何地方显式升级事务。在这个特定的事务中,我们只运行选择语句。打开 Hibernate SQL 日志记录我没有看到任何这些:
select ... for update
仅记录简单的选择语句。
似乎这里正在发生两件事之一。我对已提交读的理解完全不正确,并且已提交读隔离级别应该导致在执行选择的事务期间持有独占行级锁。或者发生了其他事情并且错误地升级了事务持有的锁。
任何帮助,将不胜感激。
编辑 1:
好的,在这方面走了很长一段曲折的道路。事实证明,这与锁定完全无关。我用来检测锁的查询已过时,并且显示锁类型为“virtualxid”。一些挖掘告诉我们,virtualxid 是每个事务对自身使用的锁,因为 PostgreSQL 内部的原因与本次讨论无关。我们对真正的独占锁进行了另一个监控查询测试,但还没有看到。
这是我们用来监视“virtualxid”锁的查询,此时它更像是一个长时间运行的查询监视器:
SELECT pg_stat_activity.datname, pg_locks.mode, pg_locks.locktype, pg_locks.granted, pg_stat_activity.usename,pg_stat_activity.query,age(now(),pg_stat_activity.query_start) AS "age", pg_stat_activity.pid
FROM pg_stat_activity,pg_locks
LEFT OUTER JOIN pg_class ON (pg_locks.relation = pg_class.oid)
WHERE
age(now(),pg_stat_activity.query_start) > interval '1 minute' AND
pg_stat_activity.datname <> 'postgres' AND
pg_locks.pid=pg_stat_activity.pid AND
pg_stat_activity.query not like '%autovacuum%' AND
pg_stat_activity.query not like '%COPY%stdout%'
order by query_start;
这是我们得到的一些输出:
<redacted> | ExclusiveLock | virtualxid | t | <redacted> | SELECT current_timestamp | 01:03:51.809594 | 22578
一个简单的 select current_timestamp 运行了一个多小时!!!
无论如何,对于那些感兴趣的人来说,看起来这些神秘的长时间运行的查询偶尔会耗尽我们的数据库连接池。因此,我们提高了连接池限制,并且实时站点恢复了嗡嗡声。我们在关键进程上设置了应用程序端超时和重试逻辑,以处理偶尔出现的问题。这些天来,我们通常至少有一个数据库线程卡在为这些奇怪执行的查询之一提供服务。绝对不理想:(
我们将尝试打开基于成本的自动吸尘器,看看这是否有助于解决问题。
编辑 2:
事实证明,这是一段很长的旅程,可能即将结束。为了应对这种行为,除了我们在上面实施的数据库查询监控之外,我们还加强了批处理错误报告。连同一些智能超时,这使我们能够将特定的应用程序用例与长时间运行的数据库查询相关联。这使我们能够对生产中出现的错误做出反应,以防止特定用途挂起 JVM 节点。
我们还能够解决为什么一个进程中长时间运行的只读 TX 会挂起连接到同一数据库的其他进程的问题。这就是事情变得有点奇怪的地方。我们使用 hibernate-memcached 将 hibernate 的二级缓存移动到一个共享的 memcached 服务器中,用于连接到同一数据库的所有 Java 进程。每当我们遇到奇怪的挂起行为时,JVM 进程中就会有大量的 memcached 客户端线程。
移除 hibernate-memcached 模块后,回到 ehcache 进行二级缓存,我们注意到奇怪的多 JVM 衰弱挂起消失了。我们仍然偶尔会收到电子邮件,告诉我们 TX 内部发生的事情超出了应有的程度。我们仍然偶尔会遇到单个 JVM 进程挂起,因为它有太多的这些长 TX 正在大规模进行。但是我们不再看到一个 JVM 中的进程以某种方式影响其他 JVM。而以前我们会看到其他节点无响应,直到我们杀死显示不良 TX 行为的初始节点。
这没有任何意义。但后来这个问题从来没有:)
——蒂姆