[Database] 데이터베이스 락, 격리수준
데이터베이스 락
- 현대 데이터베이스는
다중 사용자
를 지원하는 데이터베이스이다. - 이 말인 즉슨, 여러명의 사용자가 동시에 데이터에 접근할 수 있다는 의미이다.
- 이는 곳 race condition으로 이어져 데이터의 유실, 무결성에 오류가 발생하게된다.
- 그래서 데이터를 수정하는 경우 다른 사용자가 해당 데이터를 수정하지 못하도록 lock을 걸어 해당 데이터를 보호한다.
종류
- 일반적으로 DB에서 락을 건다고 하는 것은 아래 2개 유형 중 하나이다.
- 특정 DB의 구현 사항이 아닌, 표준적으로 적용된 사항이다.where is optimistic lock in mysql?
Optimistic Lock(낙관적 락)
-
낙관적 락은
트랜젝션이 여러 사용자가 동시에 사용되지 않을것이라고 가정
한다. - 데이터를 사용하는 동안 다른 사용자가 해당 데이터를 수정하지 못하도록 락을 건다.
Version
정보를 통해 구현한다.- 데이터를 업데이트할 때, 버전 정보를 비교해 다른 사용자가 해당 데이터를 수정하지 않았는지 확인한다.
낙관적 락 예시
- version 정보가 있는 계좌가 있다고 가정하자.
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
name TEXT,
balance DECIMAL(10,2),
version INTEGER
);
- 그리고, 계좌정보를 수정할때 아래처럼 Optimistic lock을 걸 수 있다.
- John의 계좌정보를 업데이트하는 쿼리가 어플리케이션 A와 B에서 종시에 요청되었다고 가정하자.
-- A가 계좌 정보를 읽고 업데이트하는 경우
BEGIN;
SELECT * FROM accounts WHERE id = 1;
-- 결과: {id: 1, name: 'John', balance: 1000.00, version: 1}
UPDATE accounts SET balance = 900.00, version = 2 WHERE id = 1 AND version = 1;
-- 결과: 1 row affected
COMMIT;
-- B가 동일한 계좌 정보를 업데이트하는 경우
BEGIN;
SELECT * FROM accounts WHERE id = 1;
-- 결과: {id: 1, name: 'John', balance: 1000.00, version: 1}
UPDATE accounts SET balance = 800.00, version = 2 WHERE id = 1 AND version = 1;
-- 결과: 0 rows affected
COMMIT;
- 여기서, B의 요청은 무시되게되는데, 이에따른 적절한 예외처리가 필요하다.
- 따라서 낙관적 락은
자주 충돌하는 상황에서 사용하면 성능 저하와 복잡한 코드
가 결과로 올 수 있다. 충돌하는 요청이 자주있게되면 Pessimistic Lock을 사용하는것이 적절
하다.
Pessimistic Lock
- 데이터를 사용하는 동안 다른 사용자가 해당 데이터를 사용하지 못하도록
미리 락을 건다.
- SELECT FOR UPDATE 구문으로 구현할 수 있다.
비관적 락 예시
테이블
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id),
product_id INTEGER REFERENCES products(id),
quantity INTEGER,
amount DECIMAL(10,2),
locked BOOLEAN DEFAULT false
);
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
UPDATE orders SET amount = 200.00 WHERE id = 1;
COMMIT;
- SELECT … FOR UPDATE구문으로 주문정보를 lock한다.
mariaDB의 락 모드
- 이제 mariaDB에서 락이 어떻게 구현되어있는지 확인해보자.
- 공식문서에서 설명하길, Lock은
병행(concurrent) 트랜젝션이 특정 범위의 행을 읽거나 수정하는 것을 방지
하기위해트랜젝션이 락을 획득
한다고 되어있다. - 트랜젝션 내부적으로 깊게 OS단으로 들어가면 특정 데이터에 대한 읽거나 쓰는 행위 자체는 (read, write)는 세마포어를 통해 race condition을 막지만, 여러 명령이 수행되는 트랜젝션 단위로 봤을때 락을 걸어주는 것도 필요하다.
- 즉, OS에 뮤텍스나 세마포어가 있다면, DB에는 Lock이 있는 것이다.
shared and exclusive lock
- 이 친구들은 모두
row-level lock
이다.
Shared Lock - locks(S)
- shared lock은 서로 다른 트랜젝션들이 모두 읽는 권한만 있게된다.
- lock된 row에 대한 write는 수행될 수 없다.
Exclusive Lock - locks(X)
- row에 대한 write를 하기위해 사용되며 해당 row에 대해 다른 트랜젝션이 lock하는 것을 멈춘다.
- 이 Exclusive Lock은
isolation level
에 의존하고있다. - default level인
REPEATABLE READ
에서는 다른 트랜젝션들에게 exclusively lock된 row에대한 read를 허용한다.- for update가 아닌, 단순 select문은 허용한다는 의미.
Intention Lock
table-level lock
이다.- Intention Lock은 다수의 사용자가 동시에 lock을 걸려고할때 알림역할을 한다.
- intention shared lock(IS)는 한 트랜젝션이 shared lock을 설정하려 한다는 것을 알린다.
- intention exclusive lock(IX)는 한 트랜젝션이 exclusive lock을 설정하려 한다는 것을 알린다.
알린다고?
- MariaDB문서에서는 설명이 조금 빈약하다.
- 그래서 Mysql의 공식문서를 봤는데 조금 더 자세하게 설명되어있다. Mysql
- Intention lock은
table-level
의 락이다. - IS lock은
SELECT ... LOCK IN SHARE MODE
로 설정되며 - IX lock은
SELECT ... FOR UPDATE
로 설정된다.
- 트랜젝션이 table에서 row에대한 shared lock을 획득하기위해서는 IS lock이나 더 강한 것을 table에서 획득해야하며
- 트랜젝션이 exclusive row에 대한 exclusive lock을 획득하기위해선 IX lock을 획득해야한다.
X | IX | S | IS | |
---|---|---|---|---|
X | Conflict | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict | Compatible |
S | Conflict | Conflict | Compatible | Compatible |
IS | Conflict | Compatible | Compatible | Compatible |
- 트랜젝션이 락을 요청할때, 이미 가지고있는 락과 compatible하다면 주어지지만, conflict하다면 주어지지 못한다.
- conflict한 현재 락이 release될때까지 트랜젝션은 대기한다.
- 만약 conflict한 lock이 주어지면 deadlock의 원인이 되기때문에 주어지지 않으며, 에러가 발생시킨다.
Gap Locks
- MariaDB 10.4버전 이전까지는 기본 격리수준인 repeatable read에서만 사용될 수 있었다.
- Gap Lock은 10.5버전이후로 삭제되었다.
- 이후 버전에서는 Read COMMITED transaction isolation level을 대신 사용하라고 명시되어있다.
AUTO_INCREMENT Lock Mode
- InnoDB 테이블에서 AUTO_INCREMENT 값을 생성할때 사용되는 lock mode 이다.
- 이후 release에서 삭제될 수 있다.
Read Phenomena
- ISO SQL 92 표준에서는 3가지 다른 read phenomena에 대해 정의하고있다.
- 각각의 phenomena는 isolation level에따라 다른 결과를 가져오게된다.
Dirty Read
- 커밋되지 않은 데이터를 읽을 수 있는 것을 의미한다.
- READ UNCOMMITTED에서만 발생한다.
- 나머지 격리수준에서는 일관성을 보장한다
Non-Repeatable read
- 한 트랜젝션에서 같은 쿼리를 두 번 수행했는데, 그 사이에 해당 row에 update가 commit된 것을 의미한다.
- READ UNCOMMITTED와 READ COMMITTED에서 발생한다.
Phantom Read
- 한 트랜젝션에서 같은 쿼리를 2번 수행했는데, 그 사이에 다른 트랜젝션이 새로운 row를 삽입하거나 삭제해서 결과로 반환된 row의 갯수가 다른 것을 의미한다.
- READ UNCOMMITTED, READ COMMITTED, REAPEATABLE READ 격리수준에서 발생한다.
회피방법
- non-repeatable read와 phantom read를 막는 2가지 방법은 lock기반 병행성 제어와 다중버전 병행성 제어가 있다.
Isolation Level
Isolation level in Mariadb, official docs
- isolation level에는 4가지 종류가 존재한다.
- 기본 수준으로 REPEATABLE READ가 설정되어있다.
READ UNCOMMITTED
- SELECT 구문이 locking 없이 수행된다.
- 이 수준에서의 dirty read는 일관적이지 않다.
- dirty read라고하는
다른 트랜젝션에서 수행되었지만 커밋되지 않은 불완전한 데이터
를 읽을수도 있다. - 나머지는 READ COMMITTED와 동일하게 작동한다.
- dirty read, non-repeatable read, phantom read 모두 발생한다.
READ COMMITTED
- 일관적인 읽기를 지원한다.
- 각 읽기작업 전에 새로운 스냅샷을 생성하고 그것을 읽어들인다.
- read lock의 경우, 잠긴 레코드 외에는 자유롭게 새로운 레코드를 삽입할 수 있다.
- 최초 read쿼리와 나중의 read쿼리의 결과가 달라지는
non-repeatable read
문제가 발생할 수 있다. - 또한, read한 행의 개수가 일관적이지 않은
phantom read
가 발생할 수 있다.
REPEATABLE READ
- 트랜젝션에서 처음으로 실행한 read로 생성된 스냅샷을 읽어온다.
- 따라서 새로운 값을 얻어오기위해서는 현재 트랜젝션을 commit해야 받아올 수 있다.
- 검색된 인덱스의 범위를 잠가서 다른 세션(트랜젝션)이 현재 범위에 포함된 GAP에 삽입하는 것을 차단한다
phantom read
가 발생한다.
Serializable
- 모든 select 문을
- autocommit이 disabled되어있다면 SELECT … LOCK IN SHARE MODE의 공유락 형식으로 변경한다.
- 각 SELECT문은 그 자체로 트랜젝션이 된다.
- 그래서 읽기전용으로 알려져있으며, 일관적인 읽기일때 직렬화되며 다른 트랜젝션을 차단할 필요가 없게된다.
- autocommit? : 데이터 변경작업이 바로바로 반영되는 것을 의미한다.
댓글남기기