多@Transactional会引发PostgreSQL多数据库锁吗?如何验证?
验证Spring事务拆分后的多事务与锁优化效果
我有一个标注了@Transactional的Service类EventsService,原deleteSentEvents方法用于按状态批量删除事件,原实现如下:
@Service @Transactional("transactionManager") public class EventsService { @Autowired private EventsRepository eventsRepository; public int deleteSentEvents() { String state = "SENT"; int deletionLimit = 10000; Pageable page = Pageable.ofSize(1000); Page<Events> eventsPage = null; do { eventsPage = eventsRepository.findEventsByState(state, page); eventsRepository.deleteInBatch(eventsPage.getContent().stream().map(Events::getId).toList()); deletedTotal += eventsPage.getNumberOfElements(); } while (eventsPage.hasNext() && deletedTotal < deletionLimit); return deletedTotal; } }
为避免长时间锁定大量行,我把删除逻辑移到了单独标注@Transactional的deleteBatch方法中,修改后的代码如下:
public int deleteSentEvents() { String state = "SENT"; int deletionLimit = 10000; Pageable page = Pageable.ofSize(1000); Page<Events> eventsPage = null; do { eventsPage = eventsRepository.findEventsByState(state, page); deleteBatch(eventsPage); deletedTotal += eventsPage.getNumberOfElements(); } while (eventsPage.hasNext() && deletedTotal < deletionLimit); return deletedTotal; } @Transactional("transactionManager") public void deleteBatch(Page<Events> eventsPage) { eventsRepository.deleteInBatch(eventsPage.getContent().stream().map(Events::getId).toList()); }
现在需要验证这个改动是否真的产生了多个事务,并且数据库锁的范围/时长确实变小。我尝试查询pg_catalog.pg_locks但没得到有效结果,求验证方法。
验证方法
一、确认多事务是否生成
- 开启Spring事务日志:在配置文件(
application.yml/application.properties)中添加日志配置,打印事务的创建和提交过程:
执行删除方法时,日志会输出logging: level: org.springframework.transaction.interceptor: TRACE org.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUGCreating new transaction、Committing transaction这类信息,每次调用deleteBatch都会触发一次完整的事务周期,以此确认多事务生效。 - 查看PostgreSQL事务日志:修改PostgreSQL的
postgresql.conf,开启事务和语句日志:
重启数据库后执行删除操作,查看数据库日志,会看到每次log_statement = 'all' log_transaction = ondeleteBatch对应独立的BEGIN和COMMIT语句,每个事务有唯一的ID。
二、验证锁的优化效果
- 精准查询pg_locks:执行删除操作的同时,用以下SQL实时监控目标表的锁(替换
events为你的表名):
对比修改前后:锁的持有时长(用当前时间减去SELECT locktype, database, relation, pid, mode, granted, query, pg_stat_activity.query_start FROM pg_catalog.pg_locks JOIN pg_catalog.pg_class ON pg_locks.relation = pg_class.oid JOIN pg_catalog.pg_stat_activity ON pg_locks.pid = pg_stat_activity.pid WHERE pg_class.relname = 'events' AND pid != pg_backend_pid();query_start)、锁定的行数(结合DELETE语句的返回行数),修改后应该能看到锁的持有时间大幅缩短,每次锁定的行数控制在1000条以内。 - 并发压测对比:模拟并发场景,比如同时发起多个读/写请求(比如查询
SENT状态事件、更新事件状态),对比修改前后请求的阻塞时长和成功率。如果修改后阻塞时间明显减少,说明锁的影响范围变小。 - 统计事务时长:用Spring AOP给
deleteBatch方法加埋点,记录每次方法的执行时间;或者在数据库层面通过pg_stat_activity查看每个事务的xact_start和query_end,计算事务持续时间,和原实现的单事务时长做对比,修改后的单事务时长应该远小于原方案。
三、关键注意点
- 原代码中
deleteSentEvents没有@Transactional注解,修改后的deleteBatch用默认的REQUIRED传播行为,每次调用都会新建独立事务,这是多事务生效的前提。 - 如果查询用的是PostgreSQL的快照隔离级别,读操作不会被行锁阻塞,可以通过并发写操作(比如更新同批次事件)来验证锁的影响。
内容的提问来源于stack exchange,提问作者Vasilis Iak
相关产品推荐
相关产品推荐

