트랜잭션을 지원하는 엔진 → InnoDB
try {
START TRANSACTION;
INSERT INTO tab a ...;
INSERT INTO tab b ...;
COMMIT ;
} catch(exception) {
ROLLBACK;
}
트랜잭션을 지원하지 않는 엔진 → MyISAM
FLUSH TABLES WITH READ LOCK
명령을 통해 획득할 수 있습니다.mysql> LOCK INSTANCE FOR BACKUP;
-- // 백업 실행
mysql> UNLOCK INSTANCE;
LOCK TABLES table_name [ READ | WRITE ]
명령을 통해 특정 테이블 락을 획득할 수 있습니다. 테이블 락은 MyISAM뿐 아니라 InnoDB 스토리지 엔진을 사용하는 테이블도 동일하게 설정할 수 있습니다. 명시적으로 획득한 잠금은 UNLOCK TABLES
명령을 통해 잠금을 반납할 수 있습니다.-- // "mylock'’01라는 문자열에 대해 잠금을 획득한다.
-- // 이미 잠금을 사용 중이면 2초 동안만 대기한다. (2초 이후 자동 잠금 해제됨)
mysql> SELECT GET_LOCK('mylock', 2);
-- // "mylock"OI라는 문자열에 대해 잠금이 설정돼 있는지 확인한다.
mysql> SELECT IS_FREE_LOCK('mylock');
-- // "mylock"이라는 문자열에 대해 획득했던 잠금을 반납(해제)한다.
mysql> SELECT RELEASE_LOCK('mylock');
-- // 3개 함수 모두 정상적으로 락을 획득하거나 해제한 경우에는 1을,
-- // 아니면 NULLOI나 0을 반환한다.
mysql> SELECT GET_LOCK('mylock_1' , 10);
-- // mylock_1에 대한 작업 실행
mysql> SELECT GET_LOCK('mylock_2' , 10);
-- // mylock_1과 mylock_2에 대한 작업 실행
mysql> SELECT RELEASE_LOCK('mylock_2');
mysql> SELECT RELEASE_LOCK('mylock_1');
-- // mylock_1과 mylock_2를 동시에 모두 해제하고자 한다면 RELEASE_ALL_LOCKS() 함수 사용
mysql> SELECT RELEASE_ALL_LOCKS();
RENAME TABLE tab_a TO tab_b
같이 테이블의 이름을 변경하는 경우 자동으로 획득하는 잠금입니다.-- // 배치 프로그램에서 별도의 임시 테이블 (rank_new)에 서비스용 랭킹 데이터를 생성
-- // 랭킹 배치가 완료되면 현재 서비스용 랭킹 테이블 (rank)을 rank_backup으로 백업하고
-- // 새로 만들어진 랭킹 테이블(rank_new)을 서비스용으로 대제하고자 하는 경우
mysql> RENAME TABLE rank TO rank_backup , rank_new TO rank;
위의 쿼리를 밑의 쿼리와 같이 변경할 경우 아주 짧은 시간이지만 rank 테이블이 존재하지 않는 순간이 생깁니다.
mysql> RENAME TABLE rank TO rank_backup;
mysql> RENAME TABLE rank_new TO rank;
INNODB_TRX
, INNODB_LOCKS
, INNODB_LOCK_WAITS
라는 테이블을 조인해서 사용innodb_autoinc_lock_mod=0 | 모든 INSERT 문장은 자동 증가 락을 사용한다. |
---|---|
innodb_autoinc_lock_mod=1 | INSERT되는 레코드의 건수를 정확히 예측할 수 있을 때는 자동 증가 락보다 가볍고 빠른 래치(뮤텍스)를 이용해 처리한다. |
innodb_autoinc_lock_mod=2 | 경량화된 래치(뮤텍스)를 사용한다. 대량 INSERT 문장이 실행되는 중에도 다른 커넥션에서 INSERT를 수행할 수 있으므로 동시 처리 성능이 높아진다. 하지만 소스 서버와 레플리카 서버의 자동 증가 값이 달라질 수 있음. |
InnoDB의 잠금은 레코드를 잠그는 것이 아니라 인덱스를 잠그는 방식입니다.
MySQL의 performance_schema와 information_schema 테이블을 이용해 잠금과 잠금 대기를 확인할 수 있습니다.
격리 수준(isolation level) : 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것
격리 수준의 종류
뒤로 갈수록 고립의 정도가 높아지며, 동시 처리 성능도 떨어지는 것이 일반적입닏.
DIRTY READ | NON-REPEATABLE READ | PHANTOM READ | |
---|---|---|---|
READ UNCOMMITED | 발생 | 발생 | 발생 |
READ COMMITED | 없음 | 발생 | 발생 |
REPEATABLE READ | 없음 | 없음 | 발생(InnoDB는 없음) |
SERIALIZABLE | 없음 | 없음 | 없음 |
Dirty read
: 어떤 트랜잭션에서 처리한 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있는 현상
→ 데이터가 나타났다가 사라졌다 하는 현상을 초래하므로 애플리케이션 개발자와 사용자를 혼란스럽게 만듭니다.
NON-REPEATABLE READ
: 반복 읽기를 했을 때 데이터 부정합의 문제가 발생합니다. (하나의 트랜잭션 내에서 반복 읽기를 수행했을 때 같은 결과값을 보장해야 합니다.)
언두 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있게 보장합니다.
모든 InnoDB의 트랜잭션은 고유한 트랜잭션 번호를 가지며, 언두 영역에 백업된 모든 레코드에는 변경을 발생시킨 트랜잭션의 번호가 포함돼 있습니다. 그리고 언두 영역의 백업된 데이터는 InnoDB 스토리지 엔진이 불필요하다고 판단하는 시점에 주기적으로 삭제합니다.
REPEATABLE READ 격리 수준에서는 MVCC를 보장하기 위해 실행 중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호보다 트랜잭션 번호가 앞선 언두 영역의 데이터는 삭제할 수가 없습니다.
다음 시나리오를 통해 이를 이해할 수 있스빈다. 사용자 A가 em_no가 500000인 사원의 이름을 변경하는 과정에서 사용자 B가 emp_no=500000인 사원을 SELECT할 때 어떤 과정을 거칠까요?
사용자 B가 트랜잭션을 시작하고 emp_no이 500000인 사원을 조회하면 Lara가 나옵니다. 사용자 B가 트랜잭션을 종료하지 않은 상황에서 사용자 B가 emp_no가 500000인 Lara의 이름을 Toto로 변경시키고 트랜잭션을 커밋시킵니다. 하지만 사용자 B의 트랜잭션은 커밋되지 않았기 때문에 emp_no가 500000인 사원을 조회했을 때 반복 가능한 읽기 수준을 보장하며 Lara를 반환해야 합니다. 사용자 B의 10번 트래잭션 안에서 실행되는 모든 SELECT 쿼리는 트랜잭션 번호 10(자신의 트랜잭션 번호)보다 작은 트랜잭션 번호에서 변경한 것만 보이게 됩니다.
PHANTOM READ
: 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다 안 보였다 하는 현상
SELECT …. FOR UPDATE 쿼리는 SELECT하는 레코드에 쓰기 잠금을 걸어야 하는데, 언두 레코드에는 잠금을 걸 수 없습니다. 그래서 SELECT … FOR UPDATE나 SELECT … LOCK IN SHARE MODE로 조회되는 레코드는 언두 영역의 변경 전 데이터를 가져오는 것이 아니라 현재 레코드의 값을 가져오게 되는 것입니다.
InnoDB 스토리지 엔진에서는 갭 락과 넥스트 키 락 덕분에 REPEATABLE READ 격리 수준에서도 이미 “PHANTOM READ”가 발생하지 않기 때문에 굳이 SERIALIZABLE을 사용할 필요성은 없습니다.