• 공간 데이터를 저장할 수 있는 데이터타입
  • 공간 데이터의 검색을 위한 공간 인덱스(R-Tree 알고리즘)
  • 공간 데이터의 연삼 함수(거리 또는 포함 관계의 처리)
mysql>SELECT *FROM tb_location
	WHERE ST_Contains(사각상자, px) --// 공간 자표가 Px가 사각 상자에 포함되는지 비교
    	AND ST_Distance_Sphere(p, px)<=5*1000 /* 5km */

 

기하학적 도형 정보를 관리 할 수 있는 데이터 타입을 제공합니다.

GEOMENTRY 타입은 나머지 3개 타입의 슈퍼 타입으로, POINT와 LINE,POLYGON 객체를 모두 저장 할 수 있습니다.

 

MBR(Minimum Bounding Rectagle) 해당도형을 감싸는 최소 크기의 사각형을 의미합니다. 이 사각형들의 포함 관계을  B-Tree 형태로 구현한 인덱스가 R-Tree 인덱스입니다.

 

R-Tree 인덱스 구조

 

 

 

일반적으로 WGS84(GPS) 기준의 위도, 경도 좌표 저장에 주로 사용됩니다. 하지만 위ㅣ도, 경도 좌표뿐아니라 CAD/CAM 소프트웨어 또는 회로 디자인 등과 같이 좌표 시스템에 기반을 둔 정보에 대해서는 모두 적용할 수 있습니다.

 

ST_Contain() 또는 ST_Within() 등과 같은 포함 관계를 비교하는 함수로 검색을 수행하는 경우에만 인덱스를 이용할 수 있습니다. '현재 사용자의 위치로부터 반경 5km 이내의 음식점 검색' 등과 같은 검색에 사용할 수 있습니다.

현재 출시된 MySQL에서는 거리를 비교하는 ST_Distance()와 ST_Distance_Sphere() 함수는 공간 인덱스를 효율적으로 사용하지 못하기 때문에 공간 인덱스를 사용할 수 있는 ST_Contains() 또는 ST_Within()을 이용해 거리 기반의 검색을 해야 합니다.

가운데 'P'가 기준점이고, 기준점으로부터 반경 거리 5km이내의 점(위치)들을 검색하려면 우선 사각 점선의 상자에 포함되는(ST_Contains()나 ST_Within()함수 이용)점들을 검색하면 됩니다. 사각형 박스와 같은 다각형으로만 연산 할 수 있으므로 반경 5km를 그리는 원을 포함하는 최소 사각형(MBR)으로 포함 관계 비교를 수행한 것입니다. 점 'P'은 기준점 P로부터 반경 5KM 이상 떨어져 있지만 최소 사각형 내에는 포합됩니다.

--//ST_Contains() 또는 ST_Within()을 이용해 "사각 상자"에 포함된 자표 Px만 검색
mysql>SELECT *FROM tb_location
      WHERE ST_Contains(사각상자, px);
mysql>SELECT *FROM tb_location
	  WHERE ST_Within(px, 사각상자);

두 함수는 비슷하지만 함수 파라미터는 반대로 해야합니다.

mysql>SELECT *FROM tb_location
	WHERE ST_Contains(사각상자, px) --//공간 좌표 Px가 사각 상자에 포합되는지 비교
    	AND ST_Distance_Sphere(p, px)<=5*1000 /* 5km */

P6을 빼고 결과를 조회하려면 조금 더 복잡한 비교가 필요합니다.

 

'스터디 > MySQL' 카테고리의 다른 글

인덱스 B-TREE  (0) 2023.03.24
MySQL의 격리 수준  (0) 2023.03.23
InnoDB 스토리지 엔진 잠금  (0) 2023.03.22
MySQL 엔진의 잠금  (0) 2023.03.21
트랜잭션  (1) 2023.03.21

 책의 "찾아보기"를 인덱스에 비유하면 책의 내용은 데이터 파일에 저장된 레코드의 주소에 비유됩니다. 데이터 베이스는 모든 데이터를 풀스캔 하기 때문에 오래 걸리지만 칼럼의 값과 해당 레코드가 저장된 주로를 키와 값의 쌍으로 삼아 인덱스를 만들어 두어 정렬을 하기때문에 빠르게 데이터를 가져올 수 있습니다.

 

B-TREE 인덱스

ㅇ데이터베이스의 인덱싱 알고리즘 가운데 가장 일반적으로 사용되고, 범용적이고 가장 먼저 도입된 알고리즘 입니다.일반적으로 B+-또는 B*-TREE가 사용됩니다.그리고 Binary 가 아닌 Balanced의 B를 의미 합니다.

칼럼값으 원래 값을 변형시키지 않고 인덱스 구초제 내에서 항상 정렬된 상태로 유지합니다.

 

최상위에 하나의 ROOT가 존재하고 그 하위에 자식 노드가 붙어 있는 형태입니다. 트리구조의 가장 하위에 있는 노드를 Leaf node라 하고, 중간 노드를 Branch node라 합니다. 인덱스와 실제 데이터가 저장된 데이터는 따로 관리되는데, 인덱스의 Leaf node는 항상 실제 데이터의 레코드를 찾아가기 위한 주솟값을 가지고 있습니다. 

 

그림에서 보면 인덱스의 키 값은 모두 정렬돼 있지만, 데이터 파일의 레코드는 정렬돼 있지 않고 임의의 순서로 저장돼 있습니다. 

*대부분 rdms의 데이터 파일에서 레코드는 특정 기준으로 정렬되지 않고 임이의 순서로 저장됩니다. 하지만 InnoDB 테이블에서 레코드는 클러스터되어 디스크에 저장되므로 기본적으로 프라이머리 키 순서로 정렬되어 저장됩니다.

위 를 보면 MyISAM 레코드 주소는 테이블의 생성 옵션에 따라 레코드가 테이블에 INSERT된 순번이거나 데이터 파일 내의 위치(Offset)입니다.  OFFSET을 RowID로 사용하며 RowID로 데이터 파일을 바로 찾을 수 있다.

 

InnoDB에서는 프라이머리키가 RowID 역활을 하며 두 인진 의 인덱스에서 가장 큰 차이점은 Secondary Index를 통해 데이터 파일의 레코드를 찾아가는 방법에 있습니다.  MyISAM 테이블은 세컨더리 인덱스가 물리적인 주소를 가지고, InnoDB 테이블은 PK를 주소처럼 사용하기 때문에 논리적인 주소를 갖는다고 할 수 있습니다. 인덱스를 통해 리프 노드에서 PK를 찾고, PK를 이용해 PK index를 한번더 검색해야 PK index의 리프 페이지에 저장된 레코드를 읽을 수 있습니다.

 

 그리고 InnoDB은 클러스터링 인덱스로 저장됩니다. 여기서 클러스터링 인덱스란 테이블의 레코드가 PK 값으로 정렬된 경우를 의미하며 클러스터링 테이블이라고도 합니다. 즉, 테이블의 레코드 순서는 PK 값에 크게 의존한다는 것이고, 만약 PK를 변경했다면 테이블 내에서 위치가 변경될 수 있습니다. 이는 레코드의 주소가 변경되었다는 의미입니다. 만약 RowId에 레코드의 물리적 주소를 가지고 있었다면 PK 값이 변경될 때마다 인덱스에 저장된 주소값도 변경해야합니다. 이러한 오버헤드를 줄이기 위해 InnoDB 테이블을 모든 Secondary Index는 레코드가 저장된 주소가 아니라 PK 값을 저장하도록 구현되어있습니다.

 

인덱스 키 추가

 새로은 키 값이 저장될 때 테이블의 스토리지 엔진에 따라 새로운 키 값이 즉시 인덱스에 저장될 수도 있고 그렇지 않을 수도 있습니다. 적절한 위치를 결정하고 레코드의 키 값과 대상 레코드의 주소 정보를 B-TREE Leaf node에 저장합니다.

 

삭제

  해당 키값이 저장된 B-Tree의 리프 노드를 찾아서 그냥 삭제 마크만 하면 작업이 완효됩니다. 이렇게 삭제 마킹된 인덱스 키 공강은 계속 그대로 방치하거나 재활용할 수 있습니다. MySQL5.5 이상 버전의 InnoDB 스토리지 엔진에서는 이 작업 또 한 버퍼링 되어 지연 처리 될 수 있으며, MySQL 서버가 내부적으로 처리합니다. MyISAM이나 MEMORY 스토리지 엔진의 테이블에서는 체인지 버퍼와 같은 기능이 없으므로 인덱스 키 삭제가 완료된 후 쿼리 실행이 완료됩니다.

 

변경

단순히 인덱스상의 키 값만 변경하는 것을 불가능 하므로 먼저 키 값을 삭제 한 후 다시 새로운 키 값을 추가하는 형태로 처리됩니다. 그러므로 InnoDB 스토리지 엔진을 사용하는 테이블에 대해서는 이 작업 모두 체인지 버퍼를 활용해 지연 처리 될 수 있습니다.

 

검색

위의 작업에 추가비용을 감당하는 이유는 빠른 검색을 위해서입니다. 트리 탐색을 하는 데 검색을 할 때만 사용하는 것이 아니라 UPDATE 또는 DELETE를 처리하기 위해 항상 해당 레코드를 먼저 검색해야 할 경우에도 사용됩니다.

 

 

 

'스터디 > MySQL' 카테고리의 다른 글

인덱스 R-TREE(공간 인덱스)  (0) 2023.03.27
MySQL의 격리 수준  (0) 2023.03.23
InnoDB 스토리지 엔진 잠금  (0) 2023.03.22
MySQL 엔진의 잠금  (0) 2023.03.21
트랜잭션  (1) 2023.03.21

   MySQL은 네 가지 격리 수준을 지원합니다. 이들 격리 수준은 동시성 제어를 위한 방법을 제공하며, 다중 트랜잭션 처리에 필수적입니다. 여러 트랜잭션이 다른 트랜잭션에서 벼경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것입니다.

 

 

READ UNCOMMITTED

  격리 수준에서는 그림 과 같이 각 트랜잭션에서의 변경내용이 COMMIT이나 ROLLBACK 여부에 상관없이 다른 트랜잭션에서 보입니다. 다른 트랜잭샨이 사용자 B가 실행하는 SELECT 쿼리의 결과에 어떤 영향을 미치는 지 볼 수 있습니다.

가장 낮은 격리 수준으로, 다른 트랜잭션에서 변경 중인 데이터를 읽을 수 있습니다. 이러한 현상을 Dirty Read라 합니다.

 

READ COMMITTED

  ORACLE 에서 기본적으로 사용되며 ,온라인 서비스에서 가장 많이 사용되는 격리 수준이며, Dirty read같은 상황은 발생하지 않습니다.

어떤 트랜잭션에서 데이터를 변경했더라도 COMMIT이 완료된 데이터만 다른 트랜잭션에서 조회할 수 있기 때문입니다.

NON-REPEATABLE READ(REPEATABLE READ 가 불가능하다)라는 부정합 문제가 있습니다.

  A용자와 B사용자가 하나의 트랜잭션에서 SELECT 쿼리를 실행했을 때 항상 같은 결과값이 나와야 하는 "REPEATABLE READ" 정합성에 어긋나게 됩니다.

 

REPEATABLE READ

  MySQL InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준입니다.

언두(Undo) 공간에 백업해두고 실제 레코드 값을 변경 하며 이러한 변경 방식을 MVCC라고 하고, REPEATABLE READ는 이 MVCC(Multi Version Concurrency Control)를 위해 언두 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있게 보장합니다.

READ COMMITTED도 MVCC를 이용해 COMMIT되기 전에 데이터를 보여주고 차이는 언두 영역에 백업된 레코드의 여러 버전 가운데 볓 번쨰 이전 버전까지 찾아 들어가야 하느냐에 있습니다.

  앞서 말한 것처럼 트랜잭션 안에서는 모든 결과값이 같아야하지만 아래 그림을 보면 결과는 서로 다름니다. 이렇게 다른 트랜잭션에서 수행한 변경잡업에 의해 레코드가 보였다 안 보였다 하는 현상을 PHANTOM READ 또는 PHANTOM ROW라 합니다.SELECT ... FOR UPDATE 쿼리는  SELECT하는 레코드에 쓰기 잠금을 걸어야 하는데, 언두 레코드에는 잠금을 걸 수 없습니다. 그래서 SELECT ... FOR  UPDATE나 SELECT ... LOCK IN SHARE MODE로 조회되는 레코드는 언두 영역의 변경 전 데이터를 가저오는 것이 아니라 현재 레코드의 값을 가져오게 되는 것입니다.

SERIALIZABLE

  가장 단순한 격리 수준이며 동시에 가장 엄격한 격리 수준입니다.

PHANTOM READ 현상을 방지하기 위해 트랜젝션이 완전히 종료될 때까지 다른 트랜잭션에서 해당 데이터에 대한 작업을 수행하지 못하도록 제한합니다. 이 격리 수준에서는 다른 모든 격리 수준에서 발생하는 현상이 발생하지 않습니다.

하지만 InnoDB 스토리지 엔진에서는 갭 락과 네스트 키 락 덕분에 REPEATABLE READ 격리 수준에서도 이미 "PHANTOM READ"가 발생하지 않기 때문에 굳이 SERIALIZABLE을 사용할 필요성은 없어 보입니다.

'스터디 > MySQL' 카테고리의 다른 글

인덱스 R-TREE(공간 인덱스)  (0) 2023.03.27
인덱스 B-TREE  (0) 2023.03.24
InnoDB 스토리지 엔진 잠금  (0) 2023.03.22
MySQL 엔진의 잠금  (0) 2023.03.21
트랜잭션  (1) 2023.03.21

  MySQL에서 제공하는 잠금과는 별개로 스토리지 엔진 내부에서 레코드 기반의 장금 방식입니다. 하지만 이원화된 잠금 처리 탓에 InnoDB 스토리지 엔진에서 사용되는 잠금에 대한 정보는 MySQL 명령을 이용해 접근하기가 상당히 까다롭습니다.Performance Schema를 이용해 InnoDB 스토리지 엔진의 내부 장금에 대한 모니터링 방법이 추가되었습니다.

 

  InnoDB 스토리지 엔진의 잠금 기능을 제공하며, 잠금 정보가 상당히 작은 공간으로 관리되기 때문에 레코드  락이 페이지 락으로, 또는 테이블 락으로 레벨업되는 경우는 없습니다. 일반 상용 DBMS와는 조금 다르게 InnoDB 스토리지 엔진에서는 레코드 락뿐 아니라 레코드와 레코드 사이의 간격을 잠그는 갭 락이라는 것이 합니다.

레코드 락

  레코드 자체만을 잠그는 것을 레코드 락이라 하며, 다른 상용 DBMS의 레코드 락과 동일한 역할을 합니다.

한가지 중요한 차이는 InnoDB 스토리지 엔진은 레코드 자체가 아니라 인덱스 레코드를 잠근다는 점입니다.

레코드 자체를 잠그느냐, 아니면 인덱스를 잠그느냐는 상당히 중요한 차이를 만들어 냅니다.

 

갭 락

  레코드 자체가 아니라 레코드와 바로 인접한 레코드 사이의 간격만을 잠그는 것을 의미합니다.

레코드와 레코드 사이의 간격에 새로운 레코드가 생셩되는 것을 제어하는 것입니다.

넥스트 키 락의 일부로 자주 사용됩니다.

 

넥스트 키 락

  레코드 락과 갭 락을 합쳐 놓은 형태입니다.

STATEMENT 포맥의 바이너리 로그를 사용하는 MySQL 서버에서는 REPEATABLE READ 격리수준을 하용해야 합니다.

바이너리 로그에 기록되는 쿼리가 레플리카 서버에서 실행될 때 소스 서버에서 만들어 낸 결과와 동일한 결과를 만들어내도록 보장하는 것이 주목적입니다.

데드락이나 다른 트랜잭션을 기다리게 만드는 일이 자주 발생하므로 가능하면 바이너리 로그 포맷을 ROW 형태로 바꿔서 넥스트 키 락이나 갭 락을 줄이는 것이 좋습니다.

 

자동 증가 락

  AUTO_INCREMENT라는 칼럼 속성을 제공하며 칼럼이 사용된 테이블에 동시에 여러 레코드가 INSERT되는 경우, 저장되는 각 레코드는 중복되지 않고 저장된 순서대로 증가하는 일련번호 값을 가져야 하며, auto increment lock이라고 하는 테이블 수준의 잠금을 사용합니다.

INSERT와  REPLACE 쿼리 문장과 같이 저장하는 쿼리에서만 필요합니다.

자동 증가 락은 하나만 존재하기 때문에 동시에 INSERT가 일어나는 경우 하나의 쿼리는 해당 잠금을 기다려야 하며, 자동 증가 락은 명시적으로 해제하거나 획득할 수 없습니다.

 

인덱스와 잠금

  InnoDB의 잠금은 레코드를 잠그는 것이 아니라 인덱스를 잠그는 방식으로 처리됩니다. 즉, 변경할 레코드를 찾을 때 검색한 인덱스의 레코드를 모두 잠궈야 한다. 이와 같은 특징 때문에 MySQL에서는 인덱스 설계가 굉장히 중요합니다.

 

--// 데이터베이스에 employees 테이블에는 아래와 같이 first_name 컬럼만
--// 멤버로 담긴 ix_firstname이라는 인덱스가 준비돼 있다.
--//key ix_firstname(first_name)
--//employees 테이블에서 first_name='Georgi'인 사원은 전체 253명이 있으며,
--//first_name='Georgi'이고 last_name='kLASSEN'인 사원은 딱 1명만 있는 것을 아래 쿼리로 
--//확인 할수 있다.
mysql>SELECT COUNT(*) FROM employees WHERE first_name='Georgi'
+-----------+
|       256 |
+-----------+
mysql> SELECT COUNT(*) FROM employees WHERE first_name='Georgi' AND last_name='Klassen';
+-----------+
|         1 |
+-----------+
--//employees 테이블에서 first_name='Georgi' 이고 last_name='Klassen' 인 사원의
--//입사 일자를 오늘로 변경하는 쿼리를 실행하면,
mysql> UPDATE employees SET hire_date=NOW() WHERE first_name='Georgi' AND last_name='Klassen'

 

위의 쿼리를 실행하면 1개의 UPDATE 쿼리를 위해 몇 개의 레코드에 락을 걸어야 할까?
first_name에는 인덱스가 존재하지만 last_name에는 인덱스가 없기 때문에 first_name='Georgi' 인 레코드 253건이 모두 잠깁니다.

 

만약 인덱스가 아예 존재하지 않는다면 풀 스캔이 일어나면서 1개의 UPDATE를 위해 모든 레코드가 잠기게 됩니다. MySQL의 InnoDB에서 인덱스 설계가 중요한 이유도 이 때문입니다.

 

레코드 수준의 잠금 확인 및 해제

테이블의 레코드 수준 잠금은 테이블 수준의 잠금보다는 조금 더 복잡합니다. 테이블 잠금에서는 잠금의 대상이 테이블 자체이므로 쉽게 문제의 원인이 발견되고 해결될 수 있지만, 레코드 수준의 잠금은 테이블의 레코드 가가에 잠금이 걸리므로 그 레코드가 자주 사용되지 않는다면 오랜 시간 동안 잠겨진 상태로 남아 있어도 잘 발견되지 않습니다.

MySQL 5.1부터는 레코드 잠금과 잠금 대기에 대한 조회가 가능해져 쿼리 하나로 잠금과 잠금 대기를 바로 확인할 수 있게 되었습니다.

--// 명령이 실행된 상태의 프로세스 목록을 조회
SHOW PROCESSLIST;

--// performance_schema의 data_locks 테이블과 data_lock_waits 테이블을 조인해 
--// 잠금 대기 순서 조회

mysql>SELECT
        r.trx_id waiting_trx_id,
        r.trx_mysql_thread_id waiting_thread, 
        r.trx_query waiting_query,
        b.trx_id blocking_trx_id, 
        b.trx_mysql_thread_id blocking_thread, 
        b.trx_query blocking_query
       FROM performance_schema.data_lock_waits w 
        INNER JOIN information_schema.innodb_trx b
            ON b.trx_id = w.blocking_engine_transaction_id 
        INNER JOIN information_schema.innodb_trx r
            ON r.trx_id = w.requesting_engine_transaction_id;

만약 특정 스레드가 어떤 잠금을 가지고 있는지 더 상세히 확인하고 싶다면 performance_schema의 data_locks 테이블이 가진 컬럼을 모두 살펴보면 됩니다.

mysql> SELECT* FROM performance_schema.data_locks\G

만약 특정 스레드가 잠금을 가진 상태에서 오랜 시간 멈춰있다면, 다음과 같이 특정 스레드를 강제 종료하여 잠금 경합을 끝낼 수 있습니다.

 

mysql> KILL 17;

'스터디 > MySQL' 카테고리의 다른 글

인덱스 B-TREE  (0) 2023.03.24
MySQL의 격리 수준  (0) 2023.03.23
MySQL 엔진의 잠금  (0) 2023.03.21
트랜잭션  (1) 2023.03.21
트랜잭션 지원 메타데이터  (0) 2023.02.12

  MySQL에서 사용되는 잠금은 크게 스토리지 엔진 레벨과 MySQL 엔진 레벨로 나눌 수 있습니다. MySQL 엔진은 MySQL 서버에서 스토리지 엔진을 제외한 나머지 부분으로 이해하면 되는데, MySQL 엔진 레벨의 잠금은 모든 스토리지 엔진에 영향을 미치지만, 스토리지 엔진 레벨의 잠금은 스토리지 엔진 간 상호 영향을 미치지 않습니다. MySQL 엔진에서는 테이블 데이터 동기화를 위한 테이블 락 이외에도 테이블의 구조를 잠그는 메타데이터 락 그리고 사용자의 필요에 맞게 사용할 수 있는 네임드 락이라는 잠금 기능도 제공합니다. 

 

글로벌 락

 FLUSH TABLES WITH READ LOCK 명령어로 획득 할 수 있으며, MySQL에서 제공하는 잠금 가운데 가장 범위가 큽니다.

일단 한 세션에서 글로벌 락을 획득하면 다른 세션에서 SELECT를 제외한 대부분의 DDL 문장을 실행하는 경우 글로벌 락이 해제될 때까지 해당 문장이 대기 상태로 남게 됩니다. 글로벌 락이 영향을 미치는 법위는 MySQL 서버 전체이며, 작업 대상 테이블이나 데이터베이스가 다르더라도 동일하게 영향을 미친다. 여러 데이터베이스에 존재하는 MyISAM이나  데이터베이스가 다르더라도 동일하게 영향을 미칩니다., 여러 데이터베이스에 존재하는 MyISAM이나 MEMORY 테이블에 대해 mysqldump로 일관된 백업을 받아야 할 때는 굴로벌 락을 사용해야 합니다.

 

 InnoDB거 기본 스토리지 엔진으로 채택되면서 조금 더 가벼운 그로벌 락의 필요성이 느껴졌고. MySQL 8.0 버전부터는 Xtrabackup이나 Enterprise Backup과 같은 백업 툴들의 안정적인 실행을 위해 백업 락이 도입됬습니다.

 

테이블락

 개별 테이블 단위로 설정되는 잠금이며, 명시적 또는 묵시적으로 특정 테이블의 락을 획득할 수 있습니다.

명시적으로는 "LOCK TABLES table_name[READ : WRITE]" 명령으로 특정 테이블의 락을 획득할 수 있습니다. 테이블 락은 MyISAM뿐 아니라 InnoDB 스토리지 엔진을 사용하는 테이블도 동일하게 설정할 수 있습니다.

명시적으로 획득한 잠금은 UNLOCK TABLES 명령으로 잠금을 반납 할 수 있습니다. 명시적인 테이블 락도 특별한 상황이 아니면 애플리케이션에서 사용할 필요가 거의 없습니다.명시적으로 테이블을 잠그는 작업은 글로벌 락과 동일하게 온라인 작업에 상당한 영향을 미치기 때문입니다.

 

 묵시적인 테이블 락은 MyISAM이나 MEMORY 테이블에 데이터를 변경하는 쿼리를 실행하면 발생합니다. MySQL 서버가 데이터가 변경되는 테이블에 잠금을 설정하고 데이터를 변경한 후, 즉시 잠금을 해제하는 형태로 사용됩니다. 즉, 묵시적인 테이블 락은 쿼리가 실행되는 동안 자동으로 획득됐다가 쿼리가 완료된 후 자동으로 해제됩니다. 하지만 InnoDB 테이블의 경우 스토리지 엔진 차원에서 레코드 기반의 잠금을 제공하기 때문에 단순 데이터 벼경 쿼리로 인해 묵시적인 테이블 락이 설정되지는 않습니다. 더 정확히는 InnoDB 테이블에도 테이블 락이 설정되지만 대부분의 데이터 변경(DML) 쿼리에서는 무시되고 스키마를 변경하는 쿼리(DDL)의 경우에만 영향을 미칩니다.

 

네임드락

 GET_LOCK() 함수를 이용해 임의의 문자열에 대해 잠금을 설정할 수 있습니다. 이 잠금의 특징은 대사잉 테이블이나 레코드 또는 AUTO_INCREMENT와 같은 데이터베이스 객체가 아니라는 것입니다. 네임드 락은 단순히 사용자가 지정한 문자열(String)에 대해 획득하고 반남하는 잠금입니다. 네임드 락은 자주 사용되지는 않습니다. 예를 들어, 데이터베이스 서버 1대에 5대의 웹 서버가 접속해서 서비스하는 상황에서 5대의 웹 서버가 어떤 정보를 동기화해야 하는 요건처럼 여러 클라이언트가 상호 동기화를 처리해야 할 떄 네임드 락을 이용하면 쉽게 해결할 수 있습니다.

 

-- // "mylock" 이라는 문자열에 대해 잠금을 획득한다.

-- // 이미 잠그을 하숑 중이면 2초 동안만 대기한다.(2초 이후 자동 잠금 해제됨)

mysql>SELECT GET_LOCK('mylock',2);

 

-- // "mylock"이라는 문자열에 대해 장금이 설정돼 있는지 확인한다.

mysql> SELECT IS_FREE_LOCK('mylock');

 

-- // "mylock"이라는 문자열에 대해 획득했던 잠금을 반납한다.

mysql> SELECT RELEASE_LOCK('mylock');

 

-- // 3개 함수 모두 정상적으로 락을 획득하거나 해제한 경우에는 1을, 아니면 null이나 0을 반환한다.

 

MySQL 8.0 버전부터는 다음과 같이 네임드 락을 중첩해서 사용할 수 있게 됐으며,현재 세션에서 획득한 네임드 락을 한 번에 모두 해제하는 기능도 추가됐습니다.

 

mysql> SELECT GET_LOCK('mylock_1',10);

-- // mylock_1에 대한 작업 실행

mysql> SELECT GET_LOCK('mylock_2',10);

-- // 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" 같이 테이블의 이름을 변경하는 경우 자동으로 획득하는 잠금입니다. RENAME TABLE 명령의 경우 원본 이름과 변경될 이름 두 개 모두 한꺼번에 잠금을 설정한다. 또한 실시간으로 테이블을 바꿔야 하는 요건이 배치 프로그램에서 자주 발생하는데,

 

-- //배치 프로그램에서 별도의 임시 테이블(rank_new)에 서비스용 랭킹 데이터를 생성

-- //랭킹 배치가 완료되면 현재 서비스용 랭킹 테이블(rank)을 rank_backup으로 백업하고

--// 새로 만들어진 랭킹 테이블(raw_new)을 서비스용으로 대체하고자 하는 경우

mysql> RENAME TABLE rank TO rank_backup, rank_new TO rank;

 

위와 같이 하나의 RENAME TABLE 명령문에 두 개의 RENAME 작업을 한꺼번에 싱행하면 실제 애플리케이션에서는 "Table not found 'rank'" 같은 상황을 발생시키지 않고 적용하는 것이 가능합니다. 하지만 이 문장을 다음과 같이 2개로 나눠서 실행하면 아주 짧은 시간이지만 rank 테이블이 존재하지 않는 순간이 생기며, 그 순간에 실행되는 쿼리는 "Table not found 'rank'" 오류를 발생시킵니다.

 

mysql> RENAME TABLE rank TO rank_backup;

myslq> RENAME  TABLE rank_new TO rank;

 

 때로는 메타데이터 잠금과 InnoDB의 투랜잭션을 동시에 사용해야 하는 경우도 있습니다. 예를 들어, 다음과 같은 구조의 INSERT만 실행되는 로그 테이블을 가정해보면, 이 테이블은 웹 서버의 액세스 로그를 저장만 하기 때문에 UPDATE와 DELETE가 없습니다.

 

mysql>CREATE TABLE access_log(

                id BIGINT NOT NULL AUTO_INCREMENT,

                client_ip INT USINGNED,

                access_dttm TIMESTAMP,

                ...

                PRIMARY KEY(id)

                );

 

 그런데 어느 날 이 테이블의 구조를 변경해 할 요건이 발생했다면, 물론 MySQL 서버의 Online DDL을 이용해서 변경할 수도 있지만 시간이 너무 오래 걸리는 경우라면 언두 로그의 증가와 Online DDL이 실행되는 동안 누적된 Online DDL 버퍼의 크기 등 고민해야 할 문제가 많습니다. 더 큰 문제는 MySQL 서버의 DDL은 단일 스레드로 작동하기 때문에 상당히 많은 시간이 소모될 것입니다. 이때는 새로운 구조의 테이블을 생성하고 먼저 최근(1시간 직전 또는 하루 전)의 데이터까지는 프라이머리 키인 id 값을 범위별로 나눠서 여러 개의 스레드로 빠르게 복사합니다.

 

--// 테이블의 압축을 적용하기 위해서 KEY_BLOCK_SIZE=4 옵션을 추가해 신규 테이블을 생성

mysql>CREATE TABLE  access_log_new(

                id BIGINT NOT NULL AUTO_INCREMENT,

                client_ip INT USINGNED,

                access_dttm TIMESTAMP,

                ...

                PRIMARY KEY(id)

                ) KEY_BLOCK_SIZE=4;

--// 4개의 스레드를 이용해 ID 범위별로 레코드를 신규 테이블로 복사

mysql_thread1> INSERT INTO access_log_new SELECT * FROM access_log WHERE id>=0 AND id<10000;

mysql_thread2> INSERT INTO access_log_new SELECT * FROM access_log WHERE id>=10000 AND id<20000;

mysql_thread3> INSERT INTO access_log_new SELECT * FROM access_log WHERE id>=20000 AND id<30000;

mysql_thread4> INSERT INTO access_log_new SELECT * FROM access_log WHERE id>=30000 AND id<40000;

 

그리고 나머지 데이터는 다음과 같이 트랜잭션과 테이블 잠금, RENAME TABLE 명령으로 응용 프로그램의 중단 없이 실행할 수 있습니다. 이때 "남은 데이터를 복사"하는 시간 동안은 테이블의 잠금으로 인해 INSERT를 할 수 없게 됩니다. 그래서 가능하면 미리 아주 최근 데이터까지 복사해 둬야 잠금 시간을 최소화해서 서비스에 미치는 영향을 줄일 수 있습니다.

-- // 트랜잭션을 autocomit으로 실행(BEGINE이나 START TRANSACTION으로 실행하면 안됨)

mysql> SET autocommit=0;

 

-- // 작업 대상 테이블 2개에 대해 테이블 쓰기 락을 획득

mysql>LOCK TABLES access_log WRITE, access_log_new WRITE;

 

-- // 데이터를 복사

mysql>SELECT MAX(id) as @MAX_ID FROM access_log_new;

mysql>INSERT INTO access_log_new SELECT *FROM access_log WHERE pk>MAX_ID;

mysql>COMMIT;

 

-- // 새로운 테이블로 데이터 복사가 완료되면 RENAME 명령으로 새로운 테이블을 서비스로 투입

mysql> RENAME TABLE access_log TO access_log_old,access_log_new TO access_log;

mysql> UNLOCK TABLES;

 

-- // 불필요한 테이블 삭제

mysql> DROP TABLE access_log_old;

 

 

'스터디 > MySQL' 카테고리의 다른 글

MySQL의 격리 수준  (0) 2023.03.23
InnoDB 스토리지 엔진 잠금  (0) 2023.03.22
트랜잭션  (1) 2023.03.21
트랜잭션 지원 메타데이터  (0) 2023.02.12
쿼리 실행 구조  (0) 2023.01.29

MyISAM은 트랜잭션을 지원하지 않습니다.

 

INSERT INTO tab_a ....;

   IF(_is_insert1_succeed){
       INSERT INTO tab_b ....;

       IF(_is_insert2_succeed){

           // 처리 완료;

       }ELSE{

          DELETE FROM tab_a WHERE......;

          IF(_is_delete_succeed){

             //처리 실패 및 tab_a. tab_b 모두 원상 복구 완료

       }ELSE{

             // 해결 불가능한 심각한 상황 발생

             // 이제 어떻게 해야 하나?

             // tab_b에 INSERT는 안 되고, 하지만 tab_a에는 INSERT돼 버렸는데, 삭제는 안 되고...

        }

    }

}

 

코드가 장난처럼 작성한 코드 같지만 트랜잭션이 지원되지 않는 MyISAM에 레코드를 INSERT할 때 위와 같이 하지 않으면 방법이 없습니다. 코드를 이렇게 작성하지 않았다면 부분 업데이트의 결과로 쓰레기 데이터가 테이블에 남아 있을 가능성이 있습니다. 하지만 위의 코드를 트랜잭션이 지원되는 InnoDB 테이블에서 처리한다고 가정하면 다음과 같은 간단한 코드로 완벽한 구현이 가능합니다. 얼마나 깜끔한 코드로 바뀌었는가. 비즈니스 로직 처리로 이미 IF....ELSE... 로 가득한 프로그램 코드에 이런 데이터 클렌징 코드까지 넣어야 한다는 것은 정말 힘듭니다.

 

try{

    START TRANSACTION;

    INSERT INTO tab_a ....;

    INSERT INTO tab_b.....;

    COMMIT;

     } catch(exception){

         ROLLBACK;

     }

 

 

'스터디 > MySQL' 카테고리의 다른 글

InnoDB 스토리지 엔진 잠금  (0) 2023.03.22
MySQL 엔진의 잠금  (0) 2023.03.21
트랜잭션 지원 메타데이터  (0) 2023.02.12
쿼리 실행 구조  (0) 2023.01.29
플러그인 스토리지 엔진 모델  (0) 2023.01.29

+ Recent posts