2022. 7. 15. 22:38ㆍ데이터베이스
최근 운영체제를 복습하며 reader-writer problem 에 대한 토픽을 알게 되었다.
MySQL8의 기본 스토리지 엔진 InnoDB는 DDL이 아닌 이상 테이블 잠금이 아니라 레코드 기반 락을 사용한다.
문득, 어떤 식으로 락이 걸리고 어느 정도까지 제한을 두는지 궁금해졌다.
가정은 이렇다. 사실 레코드 하나의 전체에 락을 거는 것이 아니라 인덱스에 락을 거는 방식인데, 그럼 이 때, 해당 인덱스가 아닌 다른 속성들에는 락이 걸리지 않는 것인가?
만약 그렇다면, 해당 인덱스A를 제외한 커버링 인덱스B로 처리되는 또 다른 쿼리문은 해당 인덱스A 락이 풀리지 않아도 처리가 가능할까?
뭔말인지 잘 모르겠다. 일단 해보자
우선 인덱스 생성부터!
create index test1 on post (post_subject(100));
create index test2 on post (post_content(100));
post라는 테이블에 post_subject(글 제목)에 test1이라는 인덱스, post_content(글 내용)에 test2라는 인덱스를 생성한다.
왜 인덱스를 저렇게 이상하게 생성하는지에 대해서는 아랫 글을 참조하세요.
https://leezzangmin.tistory.com/36
그 후, 두 개의 터미널을 띄워놓고
START TRANSACTION;
명령으로 각각 명시적으로 트랜잭션을 시작한다. 오토커밋이 비활성화된다.
select 문 끼리는 충돌이 일어나지 않으므로,
' SELECT ...... FOR UPDATE ' 문법을 사용해서 실험을 진행한다.
두 쿼리를 날리고 나면, 두 클라이언트는 commit이 될 때까지 block 되어있다.
MySQL의 트랜잭션이 어떤 상태에 있는지 살펴보자.
SELECT * FROM information_schema.INNODB_TRX;
트랜잭션들의 상태를 볼 수 있는 명령어다.
날려보면,
결과로 보면, 두 쿼리로 인해 뒤에 날린 쿼리에 LOCK WAIT가 발생했다.
락이 발생한 원인을 추측해보자.
첫 번째 쿼리:
select post_content from post where post.post_content like "Similique quia iure necessitat%" for update;
해당 쿼리에서 PK 102번에 해당하는 레코드의 post_content 인덱스(아까 만들었던 test2 인덱스)를 LOCK 한다. 커밋이 되기 전에는 접근할 수 없다.
그 후, 두번째 쿼리:
select * from post where post.post_subject like "Ab culpa laboriosam q%" for update;
해당 쿼리도 PK 102번째에 해당하는 레코드를 post_subject 인덱스(아까 만들었던 test1 인덱스)를 통해 접근한다.
위 쿼리와 다른 것은, select 바로 뒤에 ' * '가 있다는 점이다. 따라서 인덱스가 아닌 레코드 전체에 LOCK을 걸어야 한다.
그런데, 첫 번째 쿼리에서 이미 post_subject 에 LOCK이 걸려있다.
그래서 첫 번째 쿼리가 post_subject를 반납할 때 까지 무작정 기다려야 한다.
이 상황을 개선해보자!
다시, 두 쿼리를 다르게 날려보자.
select post_content from post where post.post_content like "Similique quia iure necessitat%" for update;
select post_subject from post where post.post_subject like "Ab culpa laboriosam q%" for update;
두 쿼리를 날려봤다.
그런데, 여전히 LOCK WAIT가 발생한다......?
왜지...?
한시간 정도 삽질하며 고민한 결과. 인덱스 생성에 문제가 있었다.
아까 만들어놓았던 test1, test2 인덱스는 크기 제한때문에 텍스트의 일부 앞부분만 떼서 인덱스로 만들어놓았다.
그런데, select 질의 결과에는 모든 데이터가 나와야 하므로 인덱스를 타기는 했지만 결국 커버링 인덱스가 아닌 다시 PK를 타고 들어가서 리프노드까지 가야 한다....
요약하자면 동일한 PK를 타는 질의를 날리고 있어서 PK가 잠겨있었기 때문에 계속 LOCK WAIT가 관측되었던 것이다...!
해당 쿼리의 실행계획의 Extra 항목을 보면 'Using where'로 되어있다. 커버링 인덱스가 아니라는 뜻이다....
그럼, 어떻게 하란 말이느냐?
다른 속성에 다른 인덱스 두개를 다시 생성해보자.. 이번엔 일부가 아닌 데이터 전체를 커버할 수 있는 인덱스,,,,,,
create index idx_create_time on post(created_at);
create index idx_update_time on post(updated_at);
게시글의 생성시간과 수정시간을 뜻하는 두 개의 속성에 인덱스를 생성..
그런데,
Error Code: 2013. Lost connection to MySQL server during query 가 발생한다.
인덱스 생성이 너무 오래 걸려 타임아웃이 걸리는 이유라고 한다.
https://stackoverflow.com/questions/28742446/create-index-causes-lost-connection-to-mysql
https://stackoverflow.com/questions/15712512/mysql-workbench-how-to-keep-the-connection-alive
두 글을 보고
설정 완료. 다시 인덱스 생성..
다시 쿼리 두개를 날려보자.
start transaction;
select created_at from post where created_at='2021-08-04 12:46:17' for update;
start transaction;
select updated_at from post where updated_at='2022-01-17 18:32:03' for update;
두근두근 결과는 ?
...왜 안될까.... 인덱스만 잠길텐데 왜 LOCK WAIT 일까...
여튼 많이 해봤으니 안되는 것으로 결론..
update문에 비밀이 있나보다!
스승님들께 질문하고 나중에 다시 왜 그런지 살펴봐야겠다..
'데이터베이스' 카테고리의 다른 글
[MySQL8.0] row constructor 여러 레코드를 서로 다른 값으로 업데이트하는 방법 (1) | 2022.12.23 |
---|---|
SQL 성능튜닝 가즈아 (0) | 2022.10.24 |
MySQL8.0 Error Code: 1071. Specified key was too long 해결법 (1) | 2022.07.15 |
[MySQL8.0] B-Tree 인덱스 사용에 영향을 미치는 요소 (0) | 2022.06.17 |
ORDER BY rand() 쿼리문 튜닝하기! (0) | 2022.05.23 |