티스토리 뷰

Database/DATABASE

[MySQL] Transaction과 Loack

구글링쟁이 k9e4h 2018. 3. 22. 14:15

소스를 보고있었는데 transaction이 걸려야할 것 같은 곳에 transaction이 없었다.

이유를 물어보니 transaction을 걸면 lock이 걸린다고했다.

row transaction이 걸려야하는데 DB특성상 table transation이 걸려서? 그렇다고 했다.

그래서 찾아보았다.



MySQL에서 사용하는 Lock의 이해 

[출처] http://blog.saltfactory.net/introduce-mysql-lock/


MySQL에서 Lock은 크게 Table Lock, Global Lock, Name Lock, User Lock 이 있다. 


Table Lock


테이블락은 어떤 세션에서 테이블 자원에 엑세스하여 데이터를 읽거나 쓰기를 할때 다른 세션에서는 테이블 자원에 대한 엑세스를 제한 하는 락이다. 세션에서 명시적으로 테이블 락을 사용하면 그 세션에서 락을 해지하지 않으면 다른 세션에서는 접근을 하지 못하게 된다. 테이블 락을 사용하기 위해서는 테이블 락 권한을 가지고 있어야 가능하다.


Global Lock


정확하게 말하면 글로벌 리드 락 (Global Read Lock)이다. 현재 세션 자체에서 글로벌하게 READ LOCK을 사용할 때 flush tables 명령으로 사용할 수 있다.






Transaction Vs Locking Tables

[출처] https://stackoverflow.com/questions/4226766/mysql-transactions-vs-locking-tables


Lock :  다른 DB 사용자가 잠긴 행/테이블에 영향을 미치지 않는다, 그러나 논리가 일관된 상태로 나오는 것을 보장하지 않는다.






Defferences between transactions and locking

[출처] https://makandracards.com/makandra/31937-differences-between-transactions-and-locking

트랜잭션을 사용할 때 교착 상태가 발생하는 이유는 무엇입니까?

이 기사의 목적은 트랜잭션과 잠금이 동일하지 않다는 것을 보여주기위한 것이므로 트랜잭션을 커밋 할 때 교착 상태가 발생하는 것은 놀랄 것입니다. 예를 들어 다음과 같은 오류가 표시 될 수 있습니다.

ActiveRecord::StatementInvalid: Mysql::Error: Lock wait timeout exceeded; try restarting transaction

이것은 우리가 아래에서 이야기하는 애플리케이션 레벨 잠금과는 아무런 관련이 없습니다 . 여기서 일어나는 일은 MySQL이 변경을 가하면 변경 사항이 일관되게 적용되도록 데이터베이스의 행을 잠급니다. 두 개의 스레드 (두 개의 데이터베이스 연결)가 동일한 행을 다른 순서로 잠그려고하면 교착 상태가 발생할 수 있습니다.

예를 들어, 트랜잭션 A는 행 1과 행 2를 변경하려고합니다. 트랜잭션 B는 동일한 행을 다른 순서 (행 # 2, 행 # 1)로 변경하려고합니다. 다음과 같이됩니다.

  • 트랜잭션 A는 행 # 1을 잠급니다.
  • 트랜잭션 B는 행 2를 잠급니다.
  • 트랜잭션 A는 행 # 1을 잠그려고 시도하지만 행 1을 잠글 수 없습니다.
  • 트랜잭션 B는 행 # 2를 잠그려고 시도하지만 행 2를 잠글 수 없습니다.
  • 두 거래는 영원히 기다린다.
  • 50 초 후에 MySQL이 시간 초과되고 두 데이터베이스 연결 모두에서 오류가 발생합니다.
  • 두 레일즈 작업자 프로세스 모두 ActiveRecord::StatementInvalid혼란스러운 오류 메시지로 인해 발생합니다.


Use locks to prevent concurrent data access

Use locks to ensure that a critical piece of code only runs in a single thread at the same time. A lock is also called a "mutex".

Example: A method transfer_money should transfer an amount of money from one account to another. Here is a bad implementation that does not use locking:

COPY
def transfer_money(source_id, target_id, amount) source_account = Account.find(source_id) target_account = Account.find(target_id) source_account.balance -= amount target_account.balance += amount source_account.save! target_account.save! end

The example above has all the issues that the transaction-less copy_invoice example at the beginning of this article had. In addition, it might lose money transactions.

Let's say two users try to transfer 5 units of currency from the same source account to the same target account. Here is what can happen:

  • Thread A retrieves both accounts. It sees that source_account and target_account both have a balance of 100.
  • Thread B retrieves both accounts. It sees that source_account and target_account both have a balance of 100.
  • Thread A sets source_account.balance to 95 and target_account.balance to 105.
  • Thread A saves both accounts and terminates.
  • Thread B also sets source_account.balance to 95 and increases target_account.balance to 105.
  • Thread B saves both accounts and terminates.

We just lost the second money transfer. The account balances should be 90 and 110, but they are 95 and 105. We call this the "lost update problem". You cannot fix this with a transaction. In fact, if you think that wrapping transfer_money in a transaction will help, you should re-read this article from the top.

What you should do instead is to wrap critical code in a lock. The lock ensures only a single thread may access it at a single time:

COPY
def transfer_money(source_id, target_id, amount) Lock.acquire('money-transfer-lock') do source_account = Account.find(source_id) target_account = Account.find(target_id) source_account.balance -= amount target_account.balance += amount source_account.save! target_account.save! end end

The code example above uses the Lock class from our simple database mutex to synchronize multiple threads or worker processes on a given name ('transfer-money-lock' in the example). Only a single thread can be inside the lock at any given time. All other threads will block on Lock.acquire until the first thread exits the block and releases the lock.

Note that this is very different from transactions, which can run concurrently.


'Database > DATABASE' 카테고리의 다른 글

[MySql] DISTINCT , GROUP BY  (1) 2018.05.10
[MySql] INDEX  (0) 2018.05.08
[MySQL] Transaction과 Loack  (0) 2018.03.22
[MySql] ON DUPLICATE KEY UPDATE & VALUES  (0) 2018.03.20
[MyBatis] Transaction  (0) 2017.12.14
[SQLD] SQL 전문가 가이드 내용 정리  (2) 2017.10.17
댓글
댓글쓰기 폼