이전에 업무 도중 read lock
이 걸린 적이 있었다. 내가 걸린 것은 아니었긴 한데..
한 테이블에 한 명의 개발자는 update를 1명의 다른 개발자는 select문 하고 있는 상황인데 select 때 commit을 하지 않으면서 lock이 걸림 결국, select 때, 꼭 트랜잭션을 끝내기 위해 rollback이나 commit을 해줘야 한다고 한다.
나는 이전까지 update, delete, insert만 조심하면 되는 줄 알았는데 select도 조심해줘야 한다는 사실이 좀 놀라웠다.
그래서 그때, 찾아보고 정리해야지 하다가 읽어보기만 하고 정리는 따로 하지 않게 되어 글또 7회차 글 낼 때도 되었고 해서 전체적인 lock에 대해 정리하고 그리고 실무에서 데이터 베이스 테이블은 거의 외래키를 쓰지 않는데 그 이유도 lock과도 관련이 있으니 정리하여 보자.
뿐만 아니라 Lock
은 데이터 정합성을 보장하고 동시성 이슈를 해결할 수 있는 방식 중 하나이며 서버 개발에서 가장 중요한 부분이라고 한다. 또한 Lock을 다루면서 DeadLock이 발생하는 케이스와 이를 예방하는 방법 또한 알아보자
Lock 해결은 dba가 해주지만,
- LOCK은 좋은 걸까?⭕ 나쁜 걸까?❌ 뭔가 인식은 안 좋아보니까
- 어떤 때 발생하는 지
- 어떻게 사용되는지
- 어떻게 예방하는지
등은 서버 개발의 중요하니 정리하는 것이 좋은 것 같음
일반적으로 하나의 DB 서버에는 여러대의 애플리케이션이 붙어서 동시에 접근하여 데이터를 읽고 쓰게 된다.
이러한 상황에서는 Race Condition(경합상태)
와 같은 동시성 이슈가 발생할 수 있는데, MySQL을 비롯한 관계형 데이터베이스는 이러한 상황에서 애플리케이션의 고가용성
(HA(high availability) : 서버, 네트워크, 프로그램 등의 정보 시스템이 상당히 오랜 기간 동안 지속적으로 정상 운영이 가능한 성질)을 보장하기 위해 다양한 형태의 Lock을 제공한다.
Lock은 결국 트랜잭션 처리에서 서로 충돌 없이 안전하게 데이터를 읽고 쓸 수 있도록 순서를 보장해주는 장치이다.
Lock
은 DB 내부(예: S락, X락, Intention Lock)와 애플리케이션 레벨(낙관적 락, 비관적 락) 양쪽에서 다양하게 적용되며, 데이터 무결성과 고가용성을 확보하기 위한 핵심 기법이다.
Lock의 종류
에는 S락·X락·Intention Lock은 DB 엔진 내부에서 실제로 “어떤 데이터를 잠그는가”에 관한 물리적 락이 있고, 낙관적/비관적 락
그 위에서 애플리케이션 트랜잭션이 “언제, 어떤 방식으로 충돌을 방지할 것인가”를 결정하는 동시성 제어 기법의 락이 있다.
여러 트랜잭션이 동일 자원(테이블·Row·메모리 영역)을 동시에 수정·조회하려 하면서 예상치 못한 충돌이 발생하는 상황
둘 이상의 트랜잭션이 서로가 보유한 자원을 해제해주길 기다리다가 무한 대기에 빠지는 상태
여기서는 mysql 기준으로 정리해보고자 한다.
흔히 읽기 락(Read Lock) 이라고 불리며, 특정 Row(레코드)에 걸리는 락이다. S라고도 한다. 읽는 동안(Read) 다른 트랜잭션이 해당 Row를 수정하지 못하도록 방지하기 위해 해당 Row에 대해 변경이 일어나지 않도록 구성하는 Read Lock으로 활용된다. 보통 SELECT ... LOCK IN SHARE MODE 로 획득
만약 해당 Row에 이 Shared Lock이 걸려있다면 Row를 Write하거나 Update하는 Lock은 사용할 수 없다. 특정 Row(레코드)에 S락이 걸려 있는 동안에는, 해당 Row가 수정(UPDATE, DELETE 등)되지 않도록 보호한는 것이다.
잠금이 걸린 상태에서는 다른 트랜잭션이 해당 Row에 대해 쓰기나 갱신을 시도할 수 없으므로, 읽는 동안 항상 동일한 결과를 얻을 수 있다.
예를 들어 동일한 Row에 접근하는 두 트랜잭션 A와 B가 동시에 발생했다고 가정하자. 트랜잭션 A는 특정 Row에 대해 SELECT를 한다. 반면 B는 동일한 Row에 UPDATE를 한다.
Q. 이 상황에서 A가 Read Lock을 획득하고 Row를 조회하는 동안 B가 UPDATE를 호출하면?
1️⃣ A가 먼저 Read Lock을 획득하고 있으므로 B의 요청은 대기
한다.
2️⃣ 이후 A가 정상적으로 데이터를 조회하고나서 해당 Row에 대해 Lock을 반납하면 B의 UPDATE 구문이 호출된다.
SELECT 작업
과UPDATE 작업
을 수행하는 트랜잭션이 발생하는 것과 달리 SELECT 작업을 하는 여러 개의 트랜잭션은 동시에 Shared Lock을 획득(공유)할 수 있다.
예를 들어 이번에는 동일한 Row를 조회하는 두 트랜잭션 A와 B가 동시에 발생했다고 가정하자. A는 특정 Row에 대해 SELECT를 한다. B도 동일한 Row에 SELECT를 한다. 이 경우 단순 데이터 조회이므로 굳이 B가 대기할 필요가 없다. B도 Shared Lock을 획득하고 해당 Row를 조회한다. 이미 트랜잭션 A가 어떤 Row에 S락을 걸어둔 상태에서, 트랜잭션 B도 똑같이 S락을 획득할 수 있음
Shared Lock
의 목적이 항상 동일한 읽기 결과를 보장하기 위함이며, 제한 없이 데이터를 읽을 수 있지만, 다른 트랜잭션이 해당 데이터를 수정할 수 없게 된다.
Shared Lock과 같이 Row 레벨(= 하나의 Row)에 적용되는 Lock 유형이지만 이 LocK은 Write/Update Lock으로 활용된다. X라고도 한다.
예를 들어 동일한 Row에 접근하는 두 트랜잭션 A와 B가 동시에 발생했다고 가정하자. A는 특정 Row에 대해 SELECT를 한다. (Shared Lock을 획득한다.) 반면 B는 동일한 Row에 UPDATE
를 한다.
Q. 이 상황에서 A가 Read Lock을 획득하고 Row를 조회하는 동안 B가 UPDATE를 호출하면?
1️⃣ A가 Shared Lock을 획득하고 있으므로 B는 UPDATE를 호출하기 전에 Exclusivce Lock을 대기한다.
2️⃣ A가 정상적으로 데이터를 조회하고나서 해당 Row에 대해 Shared Lock을 반납한다.
3️⃣ 이후 B는 Exclusvice Lock을 획득하고 UPDATE 구문이 호출된다.
즉, 동일한 Row에 대해 작업하는 여러 개의 트랜잭션은 서로 동시에 Shared Lock과 Exclusive Lock을 획득할 수 없다.
쉽게 말해 동일한 Row에 대해 A가 Shared Lock을 획득하면서 B가 Exclusive Lock을 획득할 수 없다. Shared Lock을 여러 트랜잭션이 동시에 가질 수 있지만 Exclusive Lock
은 오직 한 트랜잭션만이 가질 수 있다.
SELECT ... FOR UPDATE는 Row에 대해 이 Exclusive Lock을 거는데 다른 Lock을 사용하는 트랜잭션에서는 항상 대기하게 된다. (또다른(SELECT ... FOR UPDATE도 대기해야한다.)
1번 트랜잭션이 A레코드의 쓰기락을 얻었음에도 2번 트랜잭션은 아무 일 없이 A레코드를 조회하는데 성공한다. 왜일까?
SELECT와 S락에 대한 오해를 하면 안된다.
SELECT 한다는 것이 S락을 건다는 의미가 아니다.
SELECT, 즉 읽는 것(또는 조회)이란 어떤 '행위'일 뿐이지 '락'을 의미하진 않는다.
Table 레벨에서 걸리는 Lock 유형으로 Shared Lock의 의도(= Intention Shared)인 경우 IS, Exclusive Lock의 의도(= Intention Exclusive)인 경우 IX
테이블 전체
에 “이 테이블의 특정 Row에 Shared/Exclusive Lock을 걸겠다”는 의사를 미리 표시하여 Lock 충돌을 사전에 감지하고 DeadLock을 예방하는 목적
ALTER TABLE
, DROP TABLE
등 테이블 레벨에서 배타적인 락이 필요한 작업과는 충돌 발생. 이 경우 여러 트랜잭션이 동시에 Intention Lock을 획득할 수 없고, Exclusive Lock 대기로 이어짐Intention Lock(IS, IX)은 MySQL InnoDB 엔진에서 Row 단위 Lock과 테이블 단위 Lock이 서로 어떻게 충돌하는지를 체계적으로 관리하기 위해 존재한다. 이를 통해 불필요한 교착 상태를 줄이고, 여러 트랜잭션이 동시에 안전하게 작업할 수 있도록 돕는 중요한 기능이다.
🔥 참고 : Intention Lock은
테이블 전체
에 “어떤 행(Row)에 S/X락을 걸 의도가 있다”고 미리 표시하는 역할이고,
실제데이터(=Row) 자체
를 보호하는 잠금은 S락 또는 X락을 통해 이루어진다고 보면 된다.
“S락은 S락끼리 호환, X락은 아무것도 호환 안 됨.”
S락 ↔ S락 : 호환 ✔
S락 ↔ X락 : 호환 안 됨 ✖
X락 ↔ X락 : 당연히 호환 안 됨 ✖
SELECT ... LOCK IN SHARE MODE
수행 → A레코드에 S락 획득 SELECT ... LOCK IN SHARE MODE
수행 → S락 추가 획득 성공 SELECT ... LOCK IN SHARE MODE
수행 → A레코드에 S락 보유 UPDATE ...
또는 SELECT ... FOR UPDATE
수행 → X락 시도 아무래도 “X락 = 쓰기 락”이라는 이름 때문에, “X락이 있으면 어떤 형태의 읽기(SELECT)도 불가능하다”고 단정하기 쉽다.
하지만 실제 MySQL InnoDB(기본) 환경에서는 MVCC로 인해 단순 SELECT는 레코드의 “과거 버전”을 읽을 수도 있어서, X락을 갖고 있다고 해서 곧바로 모든 SELECT가 차단되는 것은 아니다.
즉, “X락을 걸면 다른 트랜잭션이 읽지 못한다”라고 말하는 건 개념적으로 단순화된 표현이며, 더 정확하게는 “X락이 걸려 있는 레코드에 대해 새롭게 ‘S락(=읽기 락)’을 요청하면 충돌이 발생하여 대기한다.” 라고 볼 수 있다.
SELECT
(기본 READ) 아래 내용을 핵심 포인트로 정리하자면, MySQL InnoDB에서는 “SELECT” 자체가 곧바로 S락(Shared Lock)을 획득하지 않는다는 점이 가장 중요하다.
맞다. S락 ↔ X락은 호환되지 않는다. 즉, 한 트랜잭션이 X락을 보유 중이라면, 다른 트랜잭션이 S락을 걸려고 할 때 대기 상태가 된다(또는 반대).
하지만 “SELECT(단순 조회) = S락 획득”이 아니라는 점이 포인트이다.
InnoDB에서 단순 SELECT는 락을 요청하지 않기 때문에, 사실상 S락이 아니라 “락 없이 과거 버전(MVCC)”을 읽는다고 보면 된다. 따라서 X락과 충돌이 발생하지 않아서 바로 읽기가 가능한 것임.
SELECT ... LOCK IN SHARE MODE
(S락 명시 요청) SELECT ...
(락 명시 없음) DBMS마다 SELECT 시 기본적으로 S락을 획득하도록 설계된 경우가 있다. 그런 DB에서는 트랜잭션이 X락을 잡고 있을 때 SELECT가 대기하거나 실패하는 상황이 발생할 수 있다.
MySQL InnoDB는 MVCC 기법을 통해 락 없이도 정합성 있는 데이터를 조회할 수 있는 구조이므로, “단순 SELECT가 S락을 요청하지 않는다”는 것이 특징이다.
SELECT ... FOR UPDATE;
또는 UPDATE ...
로 특정 Row에 X락 획득 SELECT ... LOCK IN SHARE MODE;
이 경우 X락 ↔ S락은 호환되지 않으므로, 두 번째 트랜잭션은 X락이 해제될 때까지 대기하게 된다.
만약 두 번째 트랜잭션이 단순
SELECT ...
만 한다면, MySQL InnoDB는 S락을 걸지 않고 버전 스냅샷을 읽어 대기 없이 바로 조회를 완료한다.
S락/X락은 DB가 Row를 물리적으로 잠그는 메커니즘
낙관적/비관적 락은 트랜잭션(또는 애플리케이션)에서 동시성 제어를 하는 전략
START TRANSACTION;
SELECT *
FROM some_table
WHERE pk = 10
FOR UPDATE; -- X락 획득
-- ... UPDATE 등 쓰기 작업 ...
COMMIT; -- 락 해제
SELECT ... FOR UPDATE
안정성
을 확보하는 과정이다. 즉, 트랜잭션이 시작될 때 Shared Lock 또는 Exclusive Lock을 걸고 시작하는 방법이다. 데이터 충돌 가능성이 높은 엔티티나 실시간 데이터 보호가 필요한 경우 적합하다.
예) 은행 계좌 잔액 관리, 재고 차감 등. 이러한 시스템에서는 안정성과 무결성이 최우선
@Transactional
public void updateAccountBalance(Long accountId, BigDecimal amount) {
// 계좌 정보를 읽으면서 PESSIMISTIC_WRITE 락을 설정합니다.
Account account = entityManager.find(Account.class, accountId, LockModeType.PESSIMISTIC_WRITE);
// 잔액을 업데이트합니다. 다른 트랜잭션은 대기 상태가 됩니다.
account.setBalance(account.getBalance().add(amount));
// 변경 사항을 저장합니다. 락은 트랜잭션 종료 시 해제됩니다.
entityManager.merge(account);
}
-- 1) SELECT pk, value, version FROM some_table WHERE pk=10;
-- 2) UPDATE some_table
-- SET value='newVal', version=version+1
-- WHERE pk=10 AND version=?
-- => 1 row affected 면 성공, 0이면 충돌(낙관적 락 실패)
혹은 JPA/Hibernate에서 @Version
필드를 이용해 자동으로 체크.
@Version
(JPA) 또는 WHERE pk=? AND version=?
(SQL) version
필드 값을 함께 가져옵니다. 이는 현재 데이터를 기준으로 비교할 기반값을 제공하는 단계입니다.version
값과 DB의 version
값을 비교합니다.version
값을 증가시킵니다. 이를 통해 데이터의 일관성을 보장합니다.충돌 가능성이 낮은 엔티티나 비즈니스 로직에서 적합
예) 게시판의 게시글 수정, 사용자 프로필 업데이트 등. 이러한 경우 동시성이 높아도 안정적으로 처리 가능
@Entity
public class UserProfile {
@Id
@GeneratedValue
private Long id;
private String name;
@Version
private int version;
// Getters and Setters
}
@Transactional
public void updateProfile(Long userId, String newName) {
// 데이터베이스에서 사용자 프로필을 조회합니다.
UserProfile profile = entityManager.find(UserProfile.class, userId);
// 조회한 사용자 이름을 변경합니다.
profile.setName(newName);
// 변경된 엔티티를 저장합니다. 만약 version이 맞지 않으면 예외가 발생합니다.
entityManager.merge(profile);
}
1) 트랜잭션 종료 시 해제
2) Autocommit 설정
autocommit=1
이면 단일 쿼리 후 자동 해제 autocommit=0
+명시적 트랜잭션 중 Commit/Rollback 누락 시 락이 길게 유지 → 주의3) 강제 해제
특징 | 낙관적 락 | 비관적 락 |
---|---|---|
충돌 가정 | 드물다 | 잦다 |
락 설정 위치 | 애플리케이션 레벨 | DB 레벨 |
성능 | 일반적으로 우수 | 동시성 처리 성능 저하 가능 |
DeadLock 위험 | 없음 | 존재 |
롤백 처리 | 애플리케이션에서 수동 처리 필요 | DB 트랜잭션에서 자동 처리 가능 |
결국 업데이트 충돌 빈도와 재시도 허용 가능성 등을 고려해 선택해야 한다. 가장 중요한 것은 트래픽 패턴, 충돌 빈도, 비즈니스 중요도에 맞춰 적절한 락 전략을 고르는 것이며, 필요하다면 여러 방식을 혼합하여 적용하는 것이 좋다.
하이브리드 접근:
특정 시점에 비관적 락 사용:
SELECT ... FOR UPDATE
를 사용하여 Row를 선점하고 중복 결제를 방지합니다. 이는 결제와 같은 중요한 트랜잭션에서 데이터 무결성을 유지하기 위한 방법입니다.비관적 락 사용:
낙관적 락 사용:
BEGIN TRANSACTION;
UPDATE table1 SET col = 'value1' WHERE id = 1;
UPDATE table2 SET col = 'value2' WHERE id = 2;
-- 오류 발생 시
ROLLBACK;
수동
으로 구현해야 함.SELECT id, name, version FROM theTable WHERE id = 2;
UPDATE theTable SET name = 'newValue', version = version + 1 WHERE id = 2 AND version = 1;
-- AffectedRows == 0이면 충돌 발생
DB 레벨에서 Timeout 설정과 애플리케이션 레벨에서 Retry 정책을 통해 방어 로직을 둘 수는 있지만, 근본적으로 DeadLock 상황 자체를 방지하는 것이 가장 좋다.
물리적인 락(예: DB 내부에서 S락·X락 등을 서로 획득하고 놓지 않는 상황)이나 애플리케이션 차원에서 잘못 설계된 락 획득 순서 등이 서로 충돌할 때, 그 결과로 나타나는 교착 상태를 “데드락”
즉, DeadLock은 특정 락(물리적/논리적)을 잘못 사용하여 “서로가 서로의 자원을 기다리는” 상황이 발생했을 때 나타나는 현상이다.
DB 내부의 물리적 락(Row Lock, Table Lock 등)이 서로 엇갈려 획득된 상황에서 주로 발생
또는 애플리케이션 트랜잭션 로직에서 서로 락을 잡는 순서가 꼬여 교착을 일으킬 수도 있음
B가 Row1을 필요로 하여 서로 대기
즉, 물리적 락이든, 애플리케이션 로직상의 논리적 락(전략)이든,
서로 자원을 점유한 상태에서 상대 자원을 기다리는 “교차 대기”가 발생하면 데드락이 형성된다.
pk=1
행에 X-Lock을 걸고 업데이트 → 아직 커밋 안 함 pk=2
행에 X-Lock을 걸고 업데이트 → 아직 커밋 안 함 pk=2
행을 UPDATE하려고 함 → B가 X-Lock 보유 중이라 대기 pk=1
행을 UPDATE하려고 함 → A가 X-Lock 보유 중이라 대기 “잘 쓰면 좋은데, 못 쓰면 문제” — 락의 양면성
(1) 잘 쓰면?
(2) 못 쓰면?
CREATE TABLE parent
(
id bigint not null primary key,
name varchar(255) null,
updated_at datetime(6) null
);
CREATE TABLE child
(
id bigint not null primary key,
name varchar(255) null,
parent_id bigint null,
CONSTRAINT parent_id_unique UNIQUE (parent_id),
CONSTRAINT child_fk FOREIGN KEY (parent_id) REFERENCES parent (id)
);
CREATE TABLE child_index
(
id bigint not null primary key,
name varchar(255) null,
parent_id bigint null,
CONSTRAINT child_index_fk FOREIGN KEY (parent_id) REFERENCES parent (id)
);
CREATE INDEX parent_id ON child_index (parent_id);
INSERT INTO parent VALUES (1, 'parent_1', NOW());
child
와 child_index
테이블은 모두 parent_id
를 외래키로 가지고 있다.
즉,
parent_id
값을 넣을 때마다, DB는 부모 테이블(parent
)에 해당 값이 실제로 존재하는지 검사해야 한다.
InnoDB에서는 이러한 무결성 체크를 위해 부모 행을 Shared Lock(S락) 으로 잠그는 동작이 발생할 수 있다.
child
테이블에 (1, 'child1', 1)
을 INSERT child
테이블의 해당 Row( id=1 )에 X-Lock(쓰기 락) 획득 parent_id = 1
이 실제로 존재하는지 확인하기 위해, parent
테이블의 id=1
행에 S-Lock(읽기 락) 획득 COMMIT
하지 않고 대기 상태결과:
child
테이블에 (1, 'child1', 1)
을 INSERT 시도 child
의 같은 Row(id=1) 에 X-Lock을 얻으려 하지만, 이미 TX1이 X-Lock 보유 중이므로 이 부분에서 대기합니다. parent
(id=1)에 대해서도 S-Lock을 또 시도 (또는 이미 획득) 결과:
parent
(id=1)를 UPDATEparent( id=1 )
을 UPDATE하려고 합니다. parent(id=1)
에 대해 S-Lock을 가지고 있거나(또는 대기 중) ⇒ S락 ↔ X락은 호환되지 않음 parent(id=1)
에 X-Lock을 얻지 못하고 대기에 빠집니다.결과:
parent( id=1 )
에 X-Lock을 얻어야 UPDATE를 마칠 수 있는데, TX2가 S-Lock을 잡고 있어서 불가능 child( id=1 )
에 X-Lock을 얻어야 INSERT를 마칠 수 있는데, TX1이 이미 X-Lock을 갖고 있어서 불가능 DB 엔진(InnoDB)은 일정 시간이 지나면 교착을 감지하여, 둘 중 한 트랜잭션을 강제로 롤백시킨다.
전체 구조 간략 사진
이후에 데드락이 생긴다.
child
)에 데이터를 넣을 때, 부모 테이블(parent
)에 해당 값이 실제 존재하는지 무결성 체크를 수행 child(id=1)
X-Lock → parent(id=1)
X-Lock 시도 parent(id=1)
S-Lock → child(id=1)
X-Lock 시도 retry
로직을 통해 해결 가능 4. 복잡한 쿼리 줄이기 & 트랜잭션 범위 짧게
5. Lock 획득 순서 일관성
6. ON DUPLICATE KEY UPDATE 주의
7. 재시도(Retry) 전략
주의
- 외래키를 전혀 쓰지 않는 것이 무조건 옳다는 뜻은 아님
- 정합성을 유지하면서 DeadLock 위험과 운영 편의성 사이에서 적절히 트레이드오프를 고려해야 함
트랜잭션 내에서 획득한 락은 “트랜잭션 범위”를 갖는다.
즉, 트랜잭션이 “시작(START TRANSACTION)”된 뒤에 실행되는 DML(SELECT ... FOR UPDATE 등 포함)로 인해 획득된 잠금들은, 그 트랜잭션이 명시적으로 끝나야(Commit/Rollback) 해제된다.
만약 autocommit=0(자동 커밋 비활성화) 상태에서 SELECT 문만 실행하고 Commit/Rollback을 생략하면, DB 입장에서는 트랜잭션이 계속 열려 있는 상태이므로 잠금도 계속 유지되는 것
Table-level Lock 사용 가능성
UPDATE
가 발생하면 쓰기 락(Write Lock)이, SELECT
가 발생하면 읽기 락(Read Lock)이 테이블 단위로 걸린다. 테이블 전체를 잠그는 방식(LOCK TABLES
등)
LOCK TABLES ...
” 로 테이블을 수동으로 잠가서 작업하는 경우, SELECT도 읽기 락을 테이블 단위로 확보 → 커밋/롤백(또는 UNLOCK TABLES
) 없이는 해제되지 않음.InnoDB라도 특정 설정(DDL·외래키 등)으로 인해 테이블 락 발생
SELECT ... LOCK IN SHARE MODE
), 혹은 다른 이유로 테이블 전체 범위를 잠그는 상황도 가능.트랜잭션 범위(autocommit=0 상태)
SELECT
가 끝나도, 트랜잭션 종료(Commit/Rollback)를 안 하면 읽기 락이 해제되지 않는 경우가 있음. 테이블 단위로 읽기 락을 보유
Lock 해제 시점 = 트랜잭션 종료
COMMIT
또는 ROLLBACK
하지 않으면, 읽기 락(READ LOCK) 상태가 그대로 유지다른 세션(개발자 A)의 UPDATE와 충돌
동시성 저하 & 병목
“왜 락이 걸렸고, 커밋/롤백을 해야 락이 풀리는가?”
- 이는 SELECT를 단순 조회 모드가 아닌, “락을 동반하거나, 특정 격리 수준에서 내부적으로 락이 필요한” 형태로 실행했기 때문이다. 그리고 이 락은 해당 트랜잭션이 종료될 때까지 유지된다.
UPDATE
, 다른 한 명은 SELECT
를 했을 뿐인데 테이블 전체 Lock이 걸렸다? LOCK TABLES
, 혹은 인덱스/DDL 상황) + SELECT에 의한 Read Lock + 트랜잭션 미종료가 겹친 결과. 정리:
- 테이블 단위로 락이 걸리는 엔진(또는 설정) 하에서, SELECT는 “읽기 락”을 잡음
- 트랜잭션이 종료되지 않으면 락이 풀리지 않아, 다른 개발자의 UPDATE(쓰기 락)와 충돌
- 이로 인해 테이블 전체가 락 걸린 것처럼 느껴지므로, SELECT 후에도 반드시 Commit/Rollback이 필요하다는 설명이 나온다.
SELECT ... FOR UPDATE
,SELECT ... LOCK IN SHARE MODE
,LOCK TABLES
)락이 풀리지 않고 계속 유지되는 이유는, 해당 쿼리를 실행한 트랜잭션이 아직 종료되지 않았기 때문이다.
- autocommit 모드인지 확인하고,
- 명시적으로 COMMIT 혹은 ROLLBACK을 해주는 습관을 들이면, 트랜잭션이 불필요하게 길어지면서 락이 묶이는 문제를 줄일 수 있다고 한다.
SELECT로도 (Read Lock) 이 걸릴 수 있고, 이를 트랜잭션이 끝날 때까지 유지하기 때문에, Commit이나 Rollback을 꼭 해줘야 한다는 이야기
위의 이유가 아니라 한 개의 열을 개발자가 update를 하려다가 커밋을 안하고, 그 페이지를 불러오면서 쿼리로 인해 그 열을 조회 하는 것 때문에 lock이 걸린 것을 수도 있다 페이지 조회 자체가 안됐었음 어쨌든 간에 lock 걸리지 않게 코드를 짤 때라던지, 직접 update문을 할 때라던지 항상 트랜잭션을 짧게 빨리 빨리 처리 해주자
참고 및 출처