JPA는 엔티티 매니저가 트랜잭션을 지원하는 쓰기 지연을 통해 트랜잭션이 종료될 때
플러시하고 커밋할 때 모든 쿼리를 데이터베이스에 전송하여 각 로우 데이터의 락을 최소한으로 잡는다고 합니다.
그래서 아래의 경우들이 락을 얼마나 잡는지 궁금했습니다.
- MyBatis를 통해 트랜잭션을 실행하는 경우
- JPA로 트랜잭션을 실행하는 경우
우선 알아야 할 것은 락의 유지시간을 확인하는 방법입니다.
MySQL을 사용한다면 performance_schema 데이터베이스를 통해서 확인할 수 있으며,
아래는 절차입니다.
- my.ini 또는 my.cnf에 아래 내용을 추가합니다.
[mysqld]
performance_schema=ON
- performance_schema.setup_instruments 테이블을 수정합니다.
트랜잭션 테이블과 락 테이블을 둘 다 사용하겠다는 의미입니다.
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME IN ('events_transactions_history_long', 'events_waits_history_long');
- performance_schema.setup_consumers 테이블을 수정합니다.
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME IN ('events_transactions_history_long', 'events_waits_history_long');
- 아래 쿼리로 락과 트랜잭션 이벤트 내역을 확인한다.
SELECT *
FROM performance_schema.events_transactions_history_long
ORDER BY TIMER_START DESC
SELECT *
FROM performance_schema.events_waits_history_long
ORDER BY TIMER_START DESC
각 항목의 내용은 아래와 같습니다.
performance_schema
- 데이터베이스 서버의 성능 및 활동을 모니터링하고 분석하는 데 사용되는 중요한 도구입니다.
setup_instruments
- 테이블은 어떤 이벤트가 모니터링되고 기록될지 결정합니다. 이벤트 인스트루먼트는 특정 종류의 이벤트(예: 락, 조건 변수, 파일 I/O 등)를 나타냅니다.
setup_consumers
- 수집된 이벤트 데이터가 어떤 소비자 테이블로 전달될지 결정합니다. 소비자 테이블은 이벤트 데이터를 저장하고 나중에 조회할 수 있도록 합니다.
event_waits_history_long
- 대기 이벤트(wait events)에 대한 기록을 저장합니다. 이 테이블은 대기 이벤트가 발생한 시점, 지속 시간, 이벤트의 유형 등을 포함한 정보를 제공합니다.
events_transactions_history_long
- 장기적인 기간 동안 발생한 트랜잭션 이벤트에 대한 상세한 정보를 제공합니다.
유의 사항
- 락은 트랜잭션이 끝날 때까지 유지가 됩니다.
- 트랜잭션이 종료되지 않는다면 다른 트랜잭션이 같은 로우데이터를 수정, 삭제를 할 수 없습니다.
- event_waits_history_long에 표시되는 각 로우데이터의 락 소요시간의 합이 events_transactions_history_long에 표시되는 트랜잭션의 소요시간과 동일하지 않습니다.
- 따라서 프로그래밍 관점에서는 event_waits_history_long의 로우 데이터를 확인하는 것보다 events_transactions_history_long의 트랜잭션 정보를 확인하는 것이 맞습니다.
시작에 앞서 테이블을 하나 만듭니다.
CREATE TABLE `test` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`content` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1. 쿼리 개수가 적을 때
1) MyBatis를 통해 트랜잭션을 실행하는 경우
@Transactional(rollbackFor = Exception.class)
public void serviceByMyBatis(){
testMapper.save("TEST - MyBatis - 1");
testMapper.save("TEST - MyBatis - 2");
testMapper.deleteByContentLike("TEST - MyBatis");
try {
Thread.sleep(5_000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
실행 결과
event_transaction_history_long
event_waits_history_long
2) JPA로 트랜잭션을 실행하는 경우
@Transactional
public void service(){
Test test1 = new Test();
test1.setContent("TEST - JPA - 1");
testRepository.save(test1);
Test test2 = new Test();
test2.setContent("TEST - JPA - 2");
testRepository.save(test2);
testRepository.deleteByContentLike("TEST - JPA");
try {
Thread.sleep(5_000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
실행 결과
event_transaction_history_long
events_waits_history_long
2. 쿼리 개수가 많을 때
1) MyBatis를 통해 트랜잭션을 실행하는 경우
@Transactional(rollbackFor = Exception.class)
public void serviceByMyBatisMultipleQuery(){
for(int i=1; i<=1_000; i++){
testMapper.save("TEST - MyBatis - " + i);
}
testMapper.deleteByContentLike("TEST - MyBatis");
}
실행 결과
event_transaction_history_long
event_waits_history_long
조회 안 됨
2) JPA로 트랜잭션을 실행하는 경우
@Transactional
public void serviceMultipleQuery(){
for(int i=1; i<=1_000; i++){
Test test = new Test();
test.setContent("TEST - JPA - " + i);
testRepository.save(test);
}
testRepository.deleteByContentLike("TEST - JPA");
}
실행 결과
event_transaction_history_long
event_waits_history_long
데이터가 너무 많아서 상위 소요시간 3건만 표시
JPA가 트랜잭션 쓰기 지원을 통해 트랜잭션 시간도 짧고 락도 최소한으로 잡는다고 생각을 하였지만
실제 측정을 했을 때는 MyBatis가 더 락을 사용하는 시간이 짧았습니다.
조금 더 자세히 알아보면 다음과 같습니다.
- JPA (쓰기 지연):
- JPA는 트랜잭션이 커밋될 때까지 변경 사항을 메모리에 저장하고, 커밋 시점에 한꺼번에 데이터베이스에 반영합니다.
- 이로 인해 트랜잭션이 길어지면 데이터베이스 락이 오래 유지될 수 있습니다.
- 트랜잭션 동안 데이터베이스와의 상호작용이 적기 때문에, 락 경합이 적을 수 있지만, 커밋 시점에 많은 작업이 몰리게 됩니다.
- MyBatis (즉시 실행):
- MyBatis는 각 SQL 쿼리를 즉시 실행하고, 각 쿼리마다 락을 획득하고 해제합니다.
- 이로 인해 트랜잭션 동안 더 자주 락을 획득하고 해제하게 됩니다.
- 트랜잭션이 길어지더라도 각 쿼리가 즉시 실행되므로, 트랜잭션이 길게 유지되는 경우에도 데이터베이스 락이 짧게 유지될 수 있습니다.
실험이 완벽한 것 같지는 않은데 표현을 그대로 받아들이기에는 애매한 부분이 있는 것 같습니다.
'[개발] 프레임워크 > Spring' 카테고리의 다른 글
ResponseBodyAdvice의 beforeBodyWrite와 String 반환형 (0) | 2024.08.08 |
---|---|
ehcache (0) | 2024.07.26 |
[JPA] java 8과 hibernate의 java.time 패키지 처리 (0) | 2024.07.03 |
Spring AOP (1) | 2024.06.26 |
Spring MVC (0) | 2024.06.23 |