업데이트:

태그: ,

카테고리:

데이터베이스 락

  • 현대 데이터베이스는 다중 사용자를 지원하는 데이터베이스이다.
  • 이 말인 즉슨, 여러명의 사용자가 동시에 데이터에 접근할 수 있다는 의미이다.
  • 이는 곳 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 Documentation

  • 이제 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? : 데이터 변경작업이 바로바로 반영되는 것을 의미한다.

댓글남기기