본문 바로가기
[개발] 프레임워크/Spring

[JPA] 트랜잭션을 지원하는 쓰기 지연은 락을 몇 초 잡을까?

by Devsong26 2024. 7. 14.

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