트랜잭션
- 작업의 완전성 보장
- 논리적 작업셋을 모두 완벽하게 처리하거나 또는 처리하지 못한경우에는 원상태로 복구하도록
- 작업의 일부만 적용되는 현상이 발생하지 않도록
- 데이터의 정합성(무결성)을 보장하기 위함
잠금
- 동시성을 제어하기 위한 기능
- 여러 커넥션에서 동시에 동일한 자원(레코드, 테이블 등)을 요청할 경우 순서대로 한시점에 하나의 커넥션만 변경할수 있도록
트랜잭션 격리수준
- 하나의 트랜잭션 내에서 또는 여러 트랜잭션 간의 작업 내용을 어떻게 공유하고 차단할 것인지의 레벨
트랜잭션 (Transaction)
1. MySQL에서의 트랜잭션
- 하나의 논리적인 작업 셋에 하나 또는 두개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나 적용되지 않거나
- MyISAM : 트랜잭션 지원하지 않음. 부분 업데이트 현상 발생가능
- InnoDB : 트랜잭션 지원함, 쿼리 중 일부라도 오류가 발생하면 실행하지 전 상태로 그대로 복구(Rollback)
2. 트랜잭션 주의사항
- 꼭 필요한 최소의 코드에만 트랜잭션을 적용하는 것이 좋음
- 프로그램 코드가 데이터베이스 커넥션을 가지고 있는 범위 최소화!
- 트랜잭션이 활성화 되어있는 프로그램의 범위를 최소화!
- 네트워크 작업이 있는 경우에는 반드시 트랜잭션에서 배제 (DBMS가 높은 부하상태로 빠질 위험이 있음)
잠금 (Lock)
MySQL 엔진 레벨 |
글로벌락, 테이블락, 유저락, 네임락 |
- 모든 스토리지 엔진에 영향을 미침 |
스토리지 엔진 레벨 |
1. MyISAM과 MEMORY 스토리지 엔진의 잠금 2. InnoDB 스토리지 엔진의 잠금 |
- 스토리지 엔진 간 상호 영향을 미치지 않음 |
1. MySQL 엔진의 잠금
종류 |
특징 |
명령 |
글로벌락 (GLOBAL LOCK) |
- MySQL에서 제공하는 잠금 중 가장 범위가 크다 - 글로벌락 획득시 SELECT를 제외한 DDL, DML 대기상태 - 영향범위 : MySQL 서버 전체 - 모든 테이블에 영향을 미치므로 가급적 사용 자제 - mysqldump로 일관된 백업시 글로벌락 사용 - mysqldump 옵션에 따라 어떤 잠금을 거는지 확인필요
|
FLUSH TABLES WITH READ LOCK -- 위 명령 실행 전에 잠금을 걸고 있는 SQL문 이 있다면 먼저 실행된 SQL문이 완료되고 해당 트랜잭션이 완료될때 까지 대기 -- FLUSH 명령어는 내부 캐시메모리를 비우기 때문에 작업중인 테이블의 작업 완료 후 실행되어야 함 -- 최악의 경우 글로벌락으로 인해 INSERT, UPDATE, DELETE 명령어가 오랫동안 대기 |
테이블락 (TABLE LOCK) |
- 개별 테이블 단위로 설정되는 잠금 - 명시적 테이블 락 : 명령으로 특정 테이블 락 획득 - 묵시적 테이블 락 : 데이터 변경되는 테이블에 자동적으로 락 설정, 변경 후 즉시잠금 해제 - InnoDB 테이블에도 테이블락이 설정되지만 레코드 기반의 잠금을 제공하기 때문에 단순 데이터 변경으로 테이블 락이 설정되지 않음 (DML에서는 거의 무시, DDL의 경우에만 영향) |
-- 명시적 테이블 락 -- 거의 사용할 필요 없음. 온라인 작업에 영향 LOCK TABLES table_name [READ|WRITE] UNLOCK TABLES -- 묵시적 테이블 락 -- 쿼리가 실행되는 동안 자동으로 획득/해제 |
유저락 (USER LOCK) |
- 단순히 사용자가 지정한 문자열에 대해 락 획득/해제
- 사용 예) 웹서버 5대, DB서버 1대 서비스 ㄴ 5대의 웹서버가 어떤 정보들을 동기화 할때 ㄴ 여러 클라이언트가 상호 동기화 처리해야 할때 |
-- "mylock"이라는 문자열에 대해 잠금 획득 -- 이미 잠금 사용중이면 2초 동안만 대기 SELECT GET_LOCK ('mylock', 2); -- "mylock" 문자열에 대해 잠금 설정 확인 SELECT IS_FREE_LOCK('mylock'); -- "mylock" 문자열에 대해 획득한 잠금 반환 SELECT RELEASE_LOCK('mylock'); -- 정상 작동 시 1 반환, 아니면 NULL, 0 반환 |
네임락 (NAME LOCK) |
- 데이터베이스 객체의 이름을 변경할때 획득하는 잠금 - 테이블, 뷰 등 - 명시적으로 획득/해제하는 것이 아님 - RENAME 명령어를 통해 변경하는 경우 자동 획득 |
-- 한번에 작업시 바로 대체 가능 RENAME TABLE rank TO rank_backup, rank_new TO rank; -- 2개로 나눠서 실행시 짧은 순간이라도 테이블이 존재하지 않는 순간이 생기므로 에러 발생 RENAME TABLE rank TO rank_backup; RENAME TABLE rank_new TO rank; |
2. MyISAM과 MEMORY 스토리지 엔진의 잠금
- MyISAM이나 MEMORY 스토리지 엔진은 자체적인 잠금을 가지지 않고 MySQL에서 제공하는 테이블락을 그대로 사용
- ARCHIVE, MERGE 등과 같은 스토리지 엔진도 동일
- 쿼리 단위로 필요한 잠금을 한꺼번에 모두 요청해서 획득하기 때문에 데드락이 발생할 수 없음
2.1. 잠금 획득
- 읽기 잠금 : 테이블에 쓰기 잠금이 걸려있지 않으면 바로 읽기 잠금을 획득하고 읽기 작업 가능
- 쓰기 잠금 : 테이블에 아무런 잠금이 걸려있지 않아야만 쓰기 잠금을 획득 가능, 다른 잠금이 걸려있다면 해제될 때까지 대기
2.2. 잠금 튜닝
- 테이블 락에 대한 작업 상황은 MySQL 상태 변수를 통해 확인 가능
2.3. 테이블 수준의 잠금 확인 및 해제
- MyISAM이나 MEMORY 등과 같은 스토리지 엔진을 사용하는 테이블은 모두 테이블 단위의 잠금 사용
- 하나의 테이블에서 전혀 다른 레코드라 하더라도 동시에 변경하는 것은 불가능하기 때문에 동시성이 떨어짐
3. InnoDB 스토리지 엔진의 잠금
- MySQL 에서 제공하는 잠금과는 별개로 스토리지 엔진 내부에서 레코드 기반의 잠금 방식을 탑재
- MyISAM보다는 훨씬 뛰어난 동시성 처리를 제공하나 이원화된 잠금 처리 탓에 InnoDB에서 사용되는 잠금에 대한 정보를 얻기 까다로움
- MySQL 5.1 부터 InnoDB 플러그인 스토리지 엔진이 도입되면서부터 트랜잭션과 잠금, 대기 등의 목록을 조회할수 있는 방법 도입
3.1. InnoDB의 잠금 방식
- 비관적잠금 (Optimistic) : 현재 변경하고자 하는 레코드를 다른 트랜잭션에서도 변경할수 있다는 가정, 먼저 잠금을 획득
- 낙관적잠금 (Pessimistic) : 각 트랜잭션이 같은 레코드를 변경할 가능성이 희박하다고 낙관적으로 가정, 작업 수행 후 잠금충돌여부 확인
3.2. InnoDB의 잠금 종류
락 종류 |
설명 |
레코드 락 |
- 레코드 자체만을 잠그는 것 (다른 상용 DBMS의 레코드락과 동일) - InnoDB 스토리지 엔진은 레코드 자체가 아니라 인덱스의 레코드를 잠금 - 인덱스가 없는 테이블이라 하더라도 자동 생성된 클러스터 인덱스를 이용해 잠금 설정
- 보조인덱스를 이용한 변경작업 : 넥스트 키 락, 갭락 사용 - primary key, unique index : 레코드 자체에 대해서만 락 설정 (갭락 사용하지 않음) |
갭 락 |
- 레코드와 바로 인접한 레코드 사이의 간격만을 잠그는 것 - 레코드와 레코드 사이의 간격에 새로운 레코드가 생성되는 것을 제어 (INSERT) |
넥스트 키 락 |
- 바이너리 로그에 기록되는 쿼리가 슬레이브에서 실행될 때 마스터에서의 결과와 동일한 결과를 보장하기위함 - 갭락, 넥스트 키락으로 인해 데드락이 발생하거나 대기현상 자주 발생 ㄴ statement 포맷방식 바이너리로그 사용 시 Repeatable read 격리 수준 이상 사용해야 함 ㄴ innodb_locks_unsafe_for_binlog =0(비활성화) 시 변경을 위해 검색하는 레코드에 넥스트 키락 잠금 사용 ㄴ 넥스트 키락과 갭락으로 인해 데드락, 대기현상이 의외로 자주 발생하므로 row포맷 바이너리 사용? ㄴ But, row포맷의 바이너리 로그 방식은 널리 사용되지 않고, 안정성 확보 어려우며, 파일사이즈가 커질수 있음 |
자동 증가 락 |
[MySQL 5.0 이하 버전] - 자동 증가하는 숫자 값을 채번하기 위해 AUTO_INCREMENT 컬럼 속성 사용 - 해당 컬럼이 사용된 테이블에 동시에 여러 레코드가 INSERT될때, 중복되지 않고 순서대로 증가된 일련번호 할당 - InnoDB 엔진에서 테이블 수준의 잠금인 AUTO_INCREMENT 락을 내부적으로 사용함 - INSERT, REPLACE 문장에서 AUTO_INCREMENT 값을 가져온느 순간만 락이 걸렸다가 즉시 해제함 - INSERT, REPLACE 문장과 같이 새로운 레코드를 저장하는 쿼리에서만 필요 (UPDATE, DELETE 문장은 불필요) - AUTO_INCREMENT 락은 테이블에 단 하나만 존재하므로, 두개의 INSERT가 동시에 실행 시 하나는 대기함 - 명시적으로 자동 증가 락을 획득하고 해제하는 방법은 없음 - 대부분 짧은 시간 동안만 걸렸다가 해제되는 잠금이기 때문에 거의 문제가 되지 않음 [MySQL 5.1 이상 버전] - innodb_autoinc_lock_mode 파라미터를 이용해 작동방식 변경 가능 - innodb_autoinc_lock_mode = 0 ㄴMySQL 5.0과 동일한 잠금 방식으로 모든 INSERT 문장은 자동 증가 락을 사용. - innodb_autoinc_lock_mode = 1 ㄴ INSERT 되는 레코드 건수를 정확이 예측가능할 때는 자동 증가락을 사용하지 않고 가볍고 빠른 래치사용 ㄴ INSERT 되는 레코드 건수를 정확이 예측불가능할 때는 MySQL 5.0에서와 같이 자동증가락 사용 ㄴ 대량 INSERT 수행시에는 한번에 여러개의 자동증가값을 할당 받아서 사용함. ㄴ 하지만, 한번에 여러개의 자동증가값 할당 받은 후 남아서 사용하지 못하면 폐기 ㄴ INSERT 문장 이후에 실행되는 INSERT 되는 레코드들 사이의 자동증가값이 누락될 수도 있음 ㄴ 최소한 하나의 INSERT문장 내에서는 연속된 자동 증가 값을 보장 : 연속모드(Consecutive mode) - innodb_autoinc_lock_mode = 2 ㄴ 절대 자동 증가락을 걸지 않고 항상 경량화된 래치(뮤텍스)사용 ㄴ 이 설정에서는 하나의 INSERT 문장으로 들어오는 레코드라 하더라도 연속된 자동 증가값을 보장하지 않음 ㄴ 인터리빙 모드 (Interleaved mode) ㄴ 대량 INSERT 문장이 실행되는 중에도 다른 커넥션에서 INSERT 수행가능 -> 동시성 처리 높아짐 ㄴ 자동 증가 기능이 유니크 한 값을 보장 ㄴ 복제를 사용하는 경우 마스터와 슬레이브의 자동증가값이 달라질 가능성도 있음 (주의) |
3.3. InnoDB의 인덱스와 잠금
- InnoDB의 잠금은 레코드를 잠그는 것이 아니라 인덱스를 잠그는 방식으로 처리
- 변경해야 할 레코드를 찾기 위해 검색한 인덱스의 레코드를 모두 잠가야 함.
- 테이블에 인덱스가 하나도 없다면, 테이블을 풀 스캔하면서 UPDATE 작업을 하게 되는데, 테이블의 모든 레코드를 잠그게 됨.
3.4. InnoDB의 트랜잭션 격리 수준과 잠금
- InnoDB의 넥스트 키 락으로 인해 불필요한 잠금 현상 발생
- 넥스트 키 락을 필요하게 만드는 주 원인은 복제를 위한 바이너리 로그 때문
- 아직 많이 사용하지는 않지만 레코드 기반의 바이너리 로그(Row based)를 사용하거나, 바이너리로그를 사용하지 않으면 넥스트 키 락의 사용을 대폭 줄일 수 있음 -> 넥스트 키 락을 줄이면, 사용자의 쿼리 요청을 동시에 많이 처리 가능
- 아래 표와 같은 조합으로 InnoDB에서 사용하는 대부분의 갭락, 넥스트 키락을 제거 가능
버전 |
설정 조합 |
설명 |
MySQL 5.0 |
innodb_locks_unsafe_for_binlog=1 트랜잭션 격리수준 : READ-COMMITTED 설정 |
|
MySQL 5.1 이상 |
[방법1] 바이너리 로그 비활성화 트랜잭션 격리수준 : READ-COMMITTED 설정 [방법2] 레코드 기반의 바이너리 로그 사용 innodb_locks_unsafe_for_binlog=1 트랜잭션 격리수준 : READ-COMMITTED 설정 |
- 유니크 키, 외래키에 대한 갭락은 없어지지 않음 - 인덱스만을 비교해서 일치하는 레코드에 대해 베타적 잠금을 걸고, 나머지 조건을 비교해서 일치하지 않는 레코드는 즉시 잠금 해제(불필요한 잠금제거 가능) |
3.5. InnoDB의 레코드 수준 잠금 확인 및 해제
- 레코드 수준의 잠금은 테이블의 레코드 각각에 잠금이 걸림
- MySQL 5.0 이하 버전에서는 레코드 잠금에 대한 메타정보를 제공하지 않음
- MySQL 5.1 이상부터 레코드 잠금과 잠금 대기에 대한 조회 가능!
- 강제로 잠금 종료 시 프로세스를 KILL 명령으로 강제 종료
MySQL 격리수준 (isolation level)
1. READ UNCOMMITTED
- COMMIT이나 ROLLBACK 여부에 상관없이 다른 트랜잭션에 의해 변경된 내용이 보여짐
- 더티리드 (Dirty Read) : 어떤 트랜잭션에서 처리한 작업이 완료(commit)되지 않았는데도 다른 트랜잭션에서 볼 수 있게 되는 현상
2. READ COMMITTED
- 오라클 기본 격리수준
- 온라인 서비스에서 가장 많이 선택되는 격리수준
- 더티리드 현상이 발생하지 않음
- 데이터 변경 시 commit 되기 전 데이터는 undo영역으로 백업되어 commit전에 다른 트랜잭션에서 동시에 조회할 경우 undo영역을 보여줌
- 어떤 트랜잭션에서 변경한 내용이 완료(commit)되기 전까지는 다른 트랜잭션에서 그러한 내역을 조회할 수 없음
- [부정합] NON-REPEATABLE READ 발생 : 하나의 트랜잭션 내에서 똑같은 SELECT 쿼리 실행 시 항상 같은 결과를 보장할 수 없음
- SELECT 시 해당 트랜잭션이 끝나기 전에 다른 트랜잭션에서 변경 된 데이터가 있으면 하나의 트랜잭션 범위 내에서도 SELECT 결과가 다를 수 있음.
3. REPEATABLE READ
- MySQL의 InnoDB 스토리지 엔진 기본 격리수준
- 더티리드 현상 발생하지 않음 (undo영역 사용)
- Non-Repeatable read 부정합 발생하지 않음
- MVCC (Multi Version Concurrency Control) : 트랜잭션 rollback 가능성을 대비해 undo 공간에 백업 후 실제 레코드 값 변경하는 방식
- MVCC를 위해 undo영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줌
- MVCC를 보장하기 위해 실행중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호보다 트랜잭션 번호가 앞선 undo영역의 데이터는 삭제 불가
- [부정합] PHANTOM READ 발생 : 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다가 안보였다가 하는 현상
- SELECT ... FOR UPDATE 쿼리는 SELECT하는 레코드에 잠금을 걸어야 하는데 undo 레코드에는 잠금을 걸수 없기 때문에
- SELECT ... FOR UPDATE , SELECT ... LOCK IN SHARE MODE 로 조회되는 레코드는 언두영역의 변경전 데이터가 아닌 현재 레코드를 가져옴
4. SERIALIZABLE
- 가장 단순하지만 가장 엄격한 격리수준
- 동시 처리 성능이 많이 떨어짐
- InnoDB테이블에서 기본적으로 순수한 SELECT 작업은 아무런 레코드 잠금이 없지만, Serializable로 설정되면 읽기작업도 공유 잠금을 획득하여 다른 트랜잭션에서 절대 접근할 수 없다.
- PHANTOM READ 발생하지 않음
|
DIRTY READ |
NON-REPEATABLE READ |
PHANTOM READ |
READ UNCOMMITTED |
발생 |
발생 |
발생 |
READ COMMITTED |
발생하지 않음 |
발생 |
발생 |
REPEATABLE READ |
발생하지 않음 |
발생하지 않음 |
발생 (InnoDB는 발생하지 않음) |
SERIALIZABLE |
발생하지 않음 |
발생하지 않음 |
발생하지 않음 |
댓글