DB/RDBMS

동시성제어? 왜 두번해야함?

Geisha 2025. 3. 21. 22:22

DB가 다 해준다며? 그런데 왜 스프링에서도 동시성 제어를 해야 하지?

 


📚 시작하며

 

요즘 DB 스터디에서 『Real MySQL 8.0』을 읽으면서 MySQL에 대한 이해를 깊게 하고 있는데, 진짜 놀란 게 하나 있었다.



“MySQL… 얘 동시성 제어에 진심이구나?”

 

정말 생각보다 정교하게 락(Lock)을 걸고,

MVCC, 세마포어, Undo 로그, 트랜잭션 격리 수준 같은 기능으로

동시에 발생하는 수많은 쿼리를 정확하게 처리하고 있었다.

 

그러던 중, 내가 참여 중이던 해커톤 프로젝트에서

스프링 애플리케이션 쪽에서도 동시성 제어가 필요한 상황이 생겼다.

 

그래서 의문이 들었다.

 

 

 


❓ 궁금했다

 

“DB에서 이렇게 철저하게 동시성 제어를 해주는데,
왜 굳이 스프링에서도 락을 걸어야 하지?”

 

처음엔 이게 잘 이해되지 않았다.

DB에서 충돌 안 나게 잘 해주면 끝나는 거 아닌가? 싶었다.


🔍 그런데 실제로 겪은 상황은 달랐다

int balance = account.getBalance(); // 1000
if (balance >= 100) {
    account.setBalance(balance - 100); // 900
    accountRepository.save(account);
}

이 코드를 두 명의 사용자가 동시에 실행했을 때,

나는 예상치 못한 결과를 보게 됐다.

A 유저balance = 1000 읽음

B 유저balance = 1000 읽음

둘 다 뺌 → 둘 다 저장

결과: 900, 원래 기대했던 balance = 800은 나오지 않았다 💥

 

이걸 보고 “DB가 아무리 정직해도, 스프링에서 동시에 요청하면 꼬일 수 있구나”를 몸으로 이해하게 됐다.
DB는 이 balance GET 요청이 수정을 위한 요청인지 모르는것이다.

 


✅ 그래서 알게 된 사실

 

🧠 DB는 “요청한 쿼리”에 대해서만 동시성 제어를 한다

DB는 우리가 보낸 SQL문만 보고 판단한다.

“이 놈이 수정할 의도인지?” 같은 건 알 수 없다.

즉, FOR UPDATE가 붙었는지, 트랜잭션이 걸려 있는지 이런 명시적 요청이 있을 때만 동시성 제어를 한다.

 

🔐 락의 단위는 “컬럼”이 아니라 “행(Row)”이다

@Lock(PESSIMISTIC_WRITE)를 붙이고 조회하면

그 레코드 전체에 락이 걸린다

컬럼 하나만 수정할 거라도, 락은 레코드 전체에 걸리는 것

 

⚙️ 동시성 제어는 DB만으로 부족한 경우가 있다

 

구간 통제 주체 설명
DB 내부 DBMS (MySQL 등) 트랜잭션, MVCC, FOR UPDATE
서비스 로직 애플리케이션 (Spring 등) 여러 스레드 간 로직 충돌 방지

 


 

🔄 스프링에서 락을 걸어야 하는 이유

 

❗ 내가 처리하고 있는 비즈니스 로직이

DB에서 값을 읽고 → 로직 처리 → 다시 저장

이걸 하나의 흐름으로 묶으려면

중간에 다른 요청이 끼어들지 않게 막아야 한다

 

그렇지 않으면?

두 개의 요청이 같은 값을 읽고

서로 다른 결과로 덮어쓰기

→ 바로 동시성 문제 발생


 

🔐 그래서 스프링에서 쓸 수 있는 락은?

종류 설명 실무 포인트
PESSIMISTIC_WRITE DB 트랜잭션 내에서 행 잠금 FOR UPDATE 쿼리로 잠금
OPTIMISTIC version 필드 비교 충돌 시 예외 발생, 재시도 필요
Redis 락 분산 환경에서 자원 선점 멀티 서버에서 주로 사용

 

 


✨ 깨달은 점

 

이전엔 단순히 “락을 건다 = 무조건 막는다” 라고 생각했지만,

락은 결국 ‘언제, 어디서, 무엇을 보호하느냐’를 명확히 해야 한다는 걸 알게 됐다.

DB는 “내가 수정할 거야” 라고 쿼리로 말해줘야 락을 건다

스프링은 “이 로직 전체를 혼자 실행하고 싶다”는 비즈니스 수준의 락을 걸어야 한다

낙관적 락은 빠르지만 충돌 시 예외가 발생할 수 있고,

사용자 입장에서 “에러났어요. 다시 시도해주세요” 로 보일 수 있다 😅


 

📌 마무리 정리

✔️ DB는 락을 걸어준다. 하지만 우리가 락을 걸겠다고 명확하게 요청해야 한다.
✔️ 동시성 제어는 애플리케이션과 DB가 협력해야만 완성된다.
✔️ 우리는 언제, 무엇에, 어떤 수준의 락을 걸지 스스로 판단할 수 있어야 한다.