Spring Boot 3 + JPA + MySQL 환경에서 특정 테이블에 대해 find 및 save 작업 시 row-level lock만 사용하고 table-level lock을 피하고 싶을 경우, 다음과 같은 전략을 사용할 수 있습니다.
✅ 핵심 개념 요약
-
MySQL InnoDB 스토리지 엔진은 기본적으로 row-level lock을 지원하며, JPA의 기본 동작은 row-level lock을 사용합니다.
-
그러나 다음 조건을 만족하지 않으면 innoDB라도 table lock이 발생할 수 있습니다:
- WHERE 절에 인덱스를 잘 사용하지 못하거나
- 락 모드가 명시되지 않았거나
UPDATE나SELECT ... FOR UPDATE에서 범위 검색을 제대로 구성하지 않았거나
✅ 전제 조건: 테이블이 InnoDB 엔진인지 확인
SHOW TABLE STATUS WHERE Name = 'your_table_name';
Engine컬럼이InnoDB여야 row-level lock이 적용됩니다.- 만약
MyISAM이면 row-level lock이 불가능하므로 반드시 InnoDB로 변경해야 합니다:
ALTER TABLE your_table_name ENGINE = InnoDB;
✅ JPA에서 row-level locking만 적용하기
1. @Lock(LockModeType.PESSIMISTIC_WRITE) 또는 @Lock(LockModeType.PESSIMISTIC_READ)
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT o FROM Order o WHERE o.id = :id")
Optional<Order> findByIdForUpdate(@Param("id") Long id);
}
PESSIMISTIC_WRITE는SELECT ... FOR UPDATE를 실행하여 row-level exclusive lock을 겁니다.- 이 쿼리는 해당 row를 다른 트랜잭션에서 변경하지 못하게 막습니다.
PESSIMISTIC_READ는 공유락을 걸며 읽기는 허용하되 쓰기는 막습니다.
⚠️ 주의: 이 방법은 반드시 트랜잭션(@Transactional) 안에서 사용되어야 실제로 락이 적용됩니다.
2. 트랜잭션 범위 명확하게 잡기
@Service
public class OrderService {
@Transactional
public void updateOrder(Long orderId) {
Order order = orderRepository.findByIdForUpdate(orderId)
.orElseThrow(...);
order.setStatus("SHIPPED");
orderRepository.save(order);
}
}
- 트랜잭션 안에서 find + update 작업을 하면 row-level lock이 보장됩니다.
- 여러 row를 다루더라도, 쿼리가 적절한 인덱스를 사용하면 table lock 없이 작동합니다.
✅ row-level lock만 보장되도록 주의할 점
📌 인덱스를 반드시 활용하라
- WHERE 조건에 Primary Key 또는 Unique Index를 활용하지 않으면 MySQL이 full table scan 후 table-level lock을 걸 수도 있습니다.
📌 범위 검색시 FOR UPDATE 주의
BETWEEN,<,>등의 조건이 있을 경우 MySQL이 gap lock 또는 next-key lock을 걸어버릴 수 있습니다.- 이를 피하려면
SELECT ... FOR UPDATE에서 정확한 key 조건을 사용하세요.
✅ 참고: Optimistic Lock vs Pessimistic Lock
- Optimistic Lock:
@Version필드를 이용, 충돌시 예외 발생. 락 자체는 없음. - Pessimistic Lock: 위에서 설명한
@Lock(LockModeType.XXX)방식. row에 DB 락이 걸림.
🧪 예시 Entity
@Entity
public class Order {
@Id
private Long id;
private String status;
@Version
private Integer version; // Optimistic Lock용
}
✅ 요약
| 목적 | 적용 방식 |
|---|---|
| row-level lock만 사용 | JPA에서 @Lock(LockModeType.PESSIMISTIC_WRITE) + @Transactional 사용 |
| table-level lock 방지 | InnoDB 사용 + PK 또는 인덱스를 통한 WHERE 조건 구성 |
| 락 충돌 방지 | 비즈니스 요구에 따라 Pessimistic 또는 Optimistic Lock 병행 고려 |