PostgreSQL不同线程提交数据后短时间内无法查询到数据的问题排查
我们有一个基于Spring Boot和Apache Camel的微服务,从Kafka Topic消费消息,完成复杂映射后将数据存入PostgreSQL的多张业务表中。
在Kafka补丁维护期间,发现同一消息可能被同时重复投递,这不符合预期。为解决该问题,我们创建了key_locking表来阻止处理重复消息的线程:
CREATE TABLE IF NOT EXISTS key_locking (key VARCHAR(50) PRIMARY KEY);
设计思路是:每个处理相同Kafka消息的线程尝试向key_locking表执行INSERT并立即DELETE对应key,成功的线程继续处理,失败的线程则阻塞等待“获胜线程”提交或回滚事务。若获胜线程提交事务,业务表中已存入对应数据;提交完成后,阻塞线程可执行INSERT和DELETE操作,之后检查业务表中是否已存在该消息的数据,若存在则将重复消息存入异常表。
对应的实现代码如下:
private boolean checkDuplicateByLocking(String key) { try { log.info("Inserting key={} into key_locking table{}", key); jdbcTemplate.update("INSERT INTO key_locking (key) VALUES (?)", key); log.info("Inserted key={} into key_locking", key); jdbcTemplate.update("DELETE FROM key_locking WHERE key=?", key); log.info("Deleted key={} from key_locking", key); var duplicateCheckResultJdbc = jdbcTemplate.queryForObject( String.format("SELECT count(*) FROM some_table WHERE key = '%s'", key), Long.class) > 0; var duplicateCheckResult = eventExtRepository.findByHdrId(key).isPresent(); log.info("Duplicate check result for jdbc template is = " + duplicateCheckResultJdbc); log.info("Duplicate check result for repository is = " + duplicateCheckResult); return duplicateCheckResultJdbc; } catch (Exception e) { log.info("Unable to lock key", e); return true; } }
我们编写了集成测试故意注入重复消息并观察日志:
大多数情况下流程符合预期,一个线程会在Inserting key=xyz into key_locking table处阻塞,直到获胜线程输出Finished processing message with key=xyz后,才会出现Inserted key=xyz into key_locking日志。但失败场景中,日志显示duplicateCheckResultJdbc为false,而duplicateCheckResult为true,调换jdbcTemplate和repository的调用顺序结果一致,两次检查的日志间隔仅1-2毫秒。
核心疑问:在READ_COMMITTED事务隔离级别下,其他线程提交的数据应全局可见,为何会出现不同线程提交数据后,另一线程无法立即查询到已提交的数据的情况?
内容的提问来源于stack exchange,提问作者Julian

