[database] Lock
Lock
데이터베이스는 여러 사용자들이 같은 데이터를 동시에 접근하는 상황에서, 데이터의 무결성과 일관성을 지키기 위해 Lock을 사용합니다. Lock 이란 트랜잭션 처리의 순차성을 보장하기 위한 방법입니다.
트랜잭션은 DB의 나누어지지 않은 최소한의 처리 단위입니다.
2022.07.12 - [database] - [database] 트랜잭션(transaction)과 격리성(Isolation) 을 참고하시면 도움이 될 것 입니다.
Lock의 설정 범위(Level)
데이터 베이스
데이터베이스 범위의 Lock은 전체 데이터베이스를 기준으로 Lock 하는 것입니다. 즉, 1개의 세션만이 DB의 데이터에 접근이 가능합니다. 해당 기능은 일반적으로 사용하지 않습니다. 사용하는 떄가 있다면 DB의 소프트웨어 버전을 올린다던지 주요한 DB의 업데이트에 사용합니다.
파일
데이터베이스 파일을 기준으로 Lock을 설정합니다. 파일 이란 테이블, row등과 같은 실제 데이터가 쓰여지는 물리적인 저장소입니다. 해당 범위의 Lock은 잘 사용되지 않습니다.
테이블
테이블 수준의 Lock은 테이블을 기준으로 Lock을 설정합니다.이는 테이블의 모든 행을 업데이트 하는 등의 전체 테이블에 영향을 주는 변경을 수행할 때 유용합니다. 즉 DDL(CREATE, ALTER, DROP) 구문과 함께 사용되며 DDL Lock이라고도 합니다.
페이지와 블록
파일의 일부인 페이지와 블록을 기준으로 Lock을 설정합니다. 잘 사용되지는 않습니다.
컬럼
컬럼 기준의 Lock은 컬럼을 기준으로 Lock을 설정할 수 있습니다. 하지만 이 형식은 Lock 설정 및 해제의 리소스가 많이 들기 때문에 일반적으로 사용되지는 않습니다. 지원하는 DBMS도 많지 않습니다.
행
행 수준의 Lock은 1개의 행(Row)를 기준으로 Lock 설정을 합니다. DML에 대한 Lock으로 가장 일반적으로 사용하는 Lock 입니다.
Lock의 종류
공유 락(Shared Lock)
공유락은 데이터를 변경하지 않는 읽기 명령에 대해 주어지는 락으로 Read Lock이라고도 불리며 Shared의 앞 글자를 따서 주로 S로 표기합니다. 여러 사용자가 동시에 데이터를 읽어도 데이터 일관성에는 아무런 영향을 주지 않기 때문에, 공유 락끼리는 동시에 접근이 가능합니다. 하지만 공유 락이 설정된 데이터에 베타 락을 사용할 수 없습니다.
베타 락(Exclusive Lock)
베타 락은 데이터에 변경을 가하는 명령들에 대해 주어지는 락으로 Write Lock으로 불리며, X로 표기합니다. 베타 락은 이름처럼 다른 세션이 해당 자원에 접근( ex. SELECT, INSERT...)하는 것을 막습니다.
이러한 점에서 베타 락은 멀티 쓰레딩 환경에서, 임계 영역을 안전하게 관리하기 위해 활용되는 뮤텍스와 유사하다고 볼 수 있습니다.
베타 락은 트랜잭션 동안 유지됩니다.
업데이트 락(Update Lock)
업데이트 락은 데이터를 수정하기 위해 베타 락(X)을 걸기 전, 데드 락을 방지하기 위해 사용되는 락입니다.
일반적으로 업데이트 락은 UPDATE쿼리의 필터(WHERE)가 실행되는 과정에서 적용됩니다.
서로 다른 트랜잭션에서 동일한 자원에 대해 읽기 쿼리 이후, 업데이트 쿼리를 작용하는 경우 컨버전 데드락이 발생하는데, 이를 막기 위해 일부 SELECT 쿼리에서도 업데이트 락을 적용(WITH(UPLOCK)) 하기도 합니다.
내재 락(Intent Lock)
내재 락은 위 락들과는 다른 기능을 합니다. 내재 락은 사용자가 요청한 범위에 대한 락(ex.테이블 락)을 걸 수 있는지 여부를 빠르게 파악하기 위해 사용되는 락입니다. 내재 락은 공유 락과 베타 락 앞에 I기호를 붙인 IS, IX, SIX 등이 있습니다.
사용자 A가 테이블의 하나의 로우에 대해 베타 락(X)을 건 경우, 사용자 B가 테이블 전체에 대한 락을 걸기 위해서는 (ex. 스키마 변경) 사용자 A의 트랜잭션이 끝날 때까지 기다려야 합니다. 그러나, 사용자 B가 테이블에 락(DDL Lock)을 걸 수 있는지 여부를 파악하기 위해 테이블에 존재하는 모든 로우와 관련된 락을 찾아보는 것은 매우 비효율적인 작업입니다.
따라서, 데이터베이스는 사용자 A가 로우에 베타 락(X)을 거는 시점에, 해당 로우의 상위 객체들(ex. 페이지, 테이블)에 대한 내재 락(IX)을 걸어, 다른 사용자가 더 큰 범위의 자원들에 대해 락을 걸 수 있는지 여부를 빠르게 파악할 수 있도록 돕습니다.
블로킹(Blocking)
블로킹은 Lock간(베타-베타, 베타-공유)의 경합이 발생해서 특정 트랜잭션이 작업을 진행하지 못하고 멈춰선 상태를 말합니다.
공유락끼리는 블로킹이 발생하지 않지만 베타락은 블로킹을 발생시킵니다. 블로킹을 해소하기 위해서는 이전의 트랜잭션이 완료(커밋 OR 롤백)되어야 합니다. 뒤에 들어온 트랜잭션은 이전 트랜잭션이 마무리되어야 이후 진행이 가능합니다. 이런 경합 성능은 좋지 않은 영향을 미칩니다. 따라서 경합을 최소화 할 필요가 있습니다.
DB를 사용하는 프로그래밍을 진행하면서 몇가지 주의사항이 있습니다.
1. 한 트랜잭션의 길이를 너무 길게하는 것은 경합의 확률을 높입니다.
2. 처음부터 설계할 때 같은 데이터를 갱신하는 트랜잭션이 동시에 수행되지 않도록 해야 합니다.
3, 트랜잭션 격리성 수준을 불필요하게 상향 조정하지 않습니다.
4. 쿼리를 오랜시간 잡아두지 않도록 적절한 튜닝을 진행합니다.
교착상태(DeadLock)
교착상태는 두 트랜잭션이 각각 Lock를 설정하고 다음 서로의 Lock에 접근하여 값을 어더오려고 할 때 이미 각각의 트랜잭션에 의해 Lock이 설정되어 있기 때문에 양쪽 트랜잭션 모두 영원히 처리가 되지 않게 되는 상태를 말합니다.
master 테이블과 detail 테이블이 있다고 가정할 때, 한 트랜잭션은 master 테이블의 1번째 row를 수정하고, detail 테이블의1 번째 row를 수정하고 다른 트랜잭션은 detail 테이블의 1번째 row를 수정한 뒤 master 테이블의 1번째 row를 수정하려고 합니다.
이 경우 첫 트랜잭션은 master 테이블의 1번째 row에 배타 락을 걸었고 두번째 트랜잭션은 detail 테이블의 1번째 row에 배타 락을 걸었습니다. 그 후 수정해야하는 detail 테이블의 1번째 row와 master 테이블의 1번째 row에 Lock을 설정하려고 하는데, 이미 각 row들은 서로 다른 트랜잭션에 의해 배타락 설정이 되어 있습니다. 따라서 Lock이 해제되기를 서로 기다리게 됩니다. 하지만 이 Lock은 풀리지 않을 서로의 트랜잭션을 기다리므로 영원히 풀리지 않을 것 입니다.
그래서 교착상태가 발생하면 DBMS가 둘 중 한 트랜잭션에 에러를 발생시킴으로써 문제를 해결합니다. 교착상태가 발생할 가능성을 줄이기 위해서는 접근 순서를 동일하게 하는 것이 중요합니다.
참고
https://sabarada.tistory.com/121