MySQL

Transaction & Locking (트랜잭션과 로킹)

iKay 2020. 3. 2. 01:22
반응형

 

RDB의 특징 중 하나는 트랜잭션이다. 흔히 트랜잭션이 ACID 특성을 가진다고 하는데, ACID는 Atomic(원자성), Consistency(일관성), Isolation(고립성) 그리고 Durability(지속성)을 일컫는다. 즉, 여러 SQL문이 로직에 의해 조합되어 실행되는 경우 하나의 단위처럼 취급해야 함을 뜻한다고 해석해도 될 듯 싶다,

 

ACID에 대한 자세한 설명은 위키를 참조하자.

https://en.wikipedia.org/wiki/ACID

 

ACID - Wikipedia

Set of properties (atomicity, consistency, isolation, durability) of database transactions intended to guarantee validity even in the event of errors, power failures, etc. In computer science, ACID (atomicity, consistency, isolation, durability) is a set o

en.wikipedia.org

 

최근 NoSQL중 하나인 Mongo DB도 4.x 버전으로 올라가면서 트랜잭션을 지원하기 시작했고, 트랜잭션이 RDB만의 특징이라고 언급할 수는 없을 것 같다. 시간이 될 때 MongoDB의 트랜잭션은 어떤 특징이 있는지 정리하는 것도 좋을 것 같다.

 

https://docs.mongodb.com/master/core/transactions/

 

Transactions — MongoDB Manual

Navigation This is an upcoming (i.e. in progress) version of the manual. Transactions In MongoDB, an operation on a single document is atomic. Because you can use embedded documents and arrays to capture relationships between data in a single document stru

docs.mongodb.com

 

(나는) 백엔드 로직을 작성할 때 RDB의 Transaction(트랜잭션) 기능을 맹신해 실수하는 경우가 많고, 부끄럽지만 아직 Locking(로킹)을 완전히 이해하지 못 하고 있다. 이번 포스팅을 계기로 트랜잭션과 로킹을 한 단계 더 이해하는 계기가 됐으면 하는 바람이다.

내가 자주 사용하는 MySQL InnoDB 위주로 정리했지만 RDB라면 맥락이 거의 비슷할 것이고, lock에는 여러 종류가 있지만, transaction을 사용할 때 exclusive lock과 shared lock을 어떻게 사용해야 하는지에 대해서만 간단히 정리했다. 하지만 그 외의 lock도 반드시 정리할 필요가 있다.

잘못된 Transaction(트랜잭션)

다음과 같이 account(계좌) money(현금)을 withdraw(인출)하는 로직을 살펴보자.

 

로직을 간단히 설명하면 다음과 같다.

accountMoney(계좌 금액) < withdrawithdrawalAmount(인출 금액) 인 경우 Transaction(이하 tx)를 rollback하고,
그렇지 않은 경우 account의 money를 withdrawal 한 만 큼 빼고, commit 한다.

 

function withdraw(accountId, withdrawalAmount) {
    tx = startTransaction();
    accountMoney = tx.query('SELECT money FROM account WHERE id = { accountId }');
    if (accountMoney  < withdrawalAmount) {
        tx.rollback();
        throw Error('잔고가 부족합니다!');
    } 
    tx.query('UPDATE account SET money = money - { withdrawalAmount } WHERE id = { accountId }');
    tx.commit();
} 

 

겉보기에는 `SELECT` 문과, `UPDATE` 문이 transaction에 묶여 있어서 이상이 없어 보이지만, 다음과 같이 TX1과 TX2가 동시에 처리되는 경우를 생각해보자.

 

현재 account의 money에는 1000원이 있고, TX1은 1000원을 인출하는 경우, TX2는 500원을 인출하는 경우이다.

 

TX1

TX2

START TRANSACTION;

SELECT money FROM account WHERE id = 1; (1)

 

 

START TRANSACTION;

SELECT money FROM account WHERE id = 1; (2)

UPDATE account SET money= money - 500 WHERE id = 1;

COMMIT; (3)

UPDATE account SET money = money-1000 WHERE id = 1; (4)

COMMIT; (5)

 

 

 

(1) TX1이 money를 조회했을 때 1000원이 남아 있다.

(2) TX2도 1000원이 남아 있음을 확인했다.

(3) Money를 1000-500 = 500 을 수행하고 commit 했다. 따라서 이 시점에서 Money는 500이다.

(4) 1000 - 1000 = 0 을 수행했다 (?)

(5) 따라서 이 시점에서 Money는 0 이다 (?)

 

(1)~(3) 은 맞지만 (4), (5)는 틀리다. (4)의 시점에서 이미 money는 500이 되었고, (1)에서 확인한 값과 다른 money-1000을 수행해서, (5)에서는 -500이 남는다!

 

이런식으로 트랜잭션을 맹신해서는 아니된다!

해결책

위와 같이 경우(`SELECT``UPDATE` 하는 경우) 로직이 일관성이 유지되게 하기 위해서는 `exclusive lock`이 필요하다. MySQL에서 `exclusive lock``SELECT ~~~ FOR UPDATE`를 사용한다. 그러면 T1이 수행되는 동안 T2가 시작되는 것을 막을 수 있다.

 

위 로직상 T1이 수행된 후 잔고가 0원이므로, T2에서 `SELECT` 한 후 잔고가 부족하다는 에러를 던지면서 `rollback` 할 것이다.

 

T1

T2

START TRANSACTION;

SELECT money FROM account WHERE id = 1 FOR UPDATE;

 

 

START TRANSACTION;

SELECT money FROM account WHERE id = 1 FOR UPDATE;

UPDATE account SET money= money - 500 WHERE id = 1;

COMMIT;

UPDATE account SET money = money-1000 WHERE id = 1'

COMMIT;

 

 

잘못된 해결책

`exclusive lock`대신 `shared lock`을 사용하면 어떻게 될까?

 

(1) s-lock을 얻는다. 이 lock은 T1이 commit 될 때 해제된다.

(2) x-lock을 얻기 위해 (1) 에서 얻어진 s-lock 이 끝나기를 기다린다. 

(3) 마찬가지로 x-lock을 얻기위해 기다려야 하는데, (2)와 (3)이 동시에 id=1 에 대해 x-lock을 얻기 위해 기다리게 되므로 dead lock이 발생한다.

T1

T2

START TRANSACTION;

SELECT money FROM account WHERE id = 1 LOCK IN SHARE MODE;  (1)

 

 

START TRANSACTION

SELECT money FROM account WHERE id = 1 LOCK IN SHARE MODE;

UPDATE account SET money= money - 500 WHERE id = 1; (2) 

COMMIT;

UPDATE account SET money = money-1000 WHERE id = 1 (3) 

COMMIT;

 

이와 같이 transaction과 lock를 같이 사용할 때 lock의 특징을 제대로 이해해야 한다.

 

Lock

MySQL InnoDB에는 앞으로 소개할 `s-lock(Shared Lock)`, `x-lock(eXclusive Lock)` 외에 더 많은 종류가 있지만 기본적으로 이 둘을 우선 이해하는 것이 필요해서 이번에는 이 둘만 정리하고 다음 포스팅 때 Lock 종류만 자세히 따로 정리할 예정이다.

 

https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html

 

MySQL :: MySQL 5.7 Reference Manual :: 14.7.1 InnoDB Locking

MySQL 5.7 Reference Manual  /  ...  /  The InnoDB Storage Engine  /  InnoDB Locking and Transaction Model  /  InnoDB Locking This section describes lock types used by InnoDB. Shared and Exclusive Locks InnoDB implements standard row-level locking where the

dev.mysql.com

 

s-lock(Shared Lock)

read 작업을 동시에 할 때 사용한다. write 작업은 할 수 없다. 다시 말해, s-lock에는 여러 s-lock 이 걸릴 수 있지만 s-lock이 걸린 상태에서는 x-lock이 걸릴 수 없다.

 

x-lock(eXclusive Lock)

x-lock이 걸리면 다른 lock을 모두 배척한다. 즉, x-lock은 한 write 작업만 가능하고, 다른 트랜잭션이 read를 결코 할 수 없다.

 

s-lock, x-lock에 대해 어떤 stackoverflow 에서 쉽게 설명하고 있어서 한글로 간단히 아래에 정리했다.

https://stackoverflow.com/questions/11837428/whats-the-difference-between-an-exclusive-lock-and-a-shared-lock

 

What's the difference between an exclusive lock and a shared lock?

According to wikipedia, Shared locks are sometimes called "read locks" and exclusive locks are sometimes called "write locks". Can you explain the reasoning behind the terms "shared" and "excl...

stackoverflow.com

교실에 칠판, 선생님, 학생들이 있을 때, 칠판은 lock 될 수 있는 대상(객체), 선생님은 writer, 학생들은 다수의 readers 라고 볼 수 있다.

1. exclusive lock 상황
- 선생님이 칠판에 무언가를 쓸 때, 여러 학생들은 선생님 등에 가려서 볼 수 없다.
  → exclusive lock이 걸린 경우 shared lock이 걸릴 수 없다.
- 다른 선생님이 와서 동시에 같은 칠판에 글을 쓸 수 없을 뿐더러 쓰게 되면 칠판의 내용이 혼란스러워진다.
  → exclusive lock이 걸린 경우 exclusive lock을 동시에 걸 수 없다.

2. shared lock 상황
- 선생님이 판서를 마치고 옆으로 비켜있는 때 여러 학생들은 동시에 칠판을 읽고 노트에 필기할 수 있다.
  → shared lock은 동시에 여러 개 얻을 수 있다.
- 선생님이 다음 내용을 판서하기 위해 학생들이 모두 필기하기를 기다린다.
  → shared lock이 한 개 이상 존재하면, exclusive lock을 걸 수 없다.

결론

트랜잭션만으로 쿼리의 조합이 결코 일관성을 가져다 줄 것이라 맹신해서는 안되며, 일관성을 갖기 위해서는 적절히 lock을 사용해야 한다. 특히 `SELECT`, `UPDATE` 문에서 말이다. 하지만, lock을 사용하게 되면 논리적인 일관성은 얻게되나, 잘 못 사용하게 되면 나쁜 성능을 내게 된다. 따라서 lock의 종류와 성질을 정확히 이해하고 사용할 필요가 있다.

 

기초가 되는 x-lock, s-lock에 대해 정말 간략히 알아봤고, lock에 대해서는 더 정리가 필요하다.

 

언젠가는 mongo db의 트랜잭션 & 로킹과 mysql의 그것들과 비교해는 글을 포스팅해보는 것도 좋을 것 같다.

반응형

'MySQL' 카테고리의 다른 글

MySQL innodb_flush_log_at_trx_commit 옵션  (0) 2023.03.03
Clustered Index와 Secondary Index  (0) 2021.01.28