반응형

#Context switcing 이란?

CPU가 어떤 프로세스를 실행하고 있는 상태에서 인터럽트에 의해 다음 우선 순위를 가진 프로세스가 실행되어야 할 때
기존의 프로세스 정보들은 PCB에 저장하고 다음 프로세스의 정보를 PCB에서 가져와 교체하는 작업을 컨텍스트 스위칭이라 한다.

context-switching

#PCB(process Control Block)란?
Process ID와 상태, 우선순위, 메모리 정보 등을 저장한다.
멀티스레드가 아닌 멀티프로세스 환경에서는 PCB가 PC(program counter)와 Register set 정보도 포함한다.

 

#TCB(Thread Control Block)란?
Thread별로 존재하는 자료구조이며, PC와 Register Set(CPU 정보), 그리고 PCB를 가리키는 포인터를 가진다.
그리고 TCB는 PCB보다 적은 데이터를 가지는 자료구조이다. 해당 Thread에 대한 정보만 저장하면 되기 때문이다

 

#Context switcing이 자주 일어나면 왜 부하가 발생할까?
메모리와 레지스터 사이의 데이터 이동도 I/O이다.
즉, 컨텍스트 스위칭 과정에서 I/O가 발생한다. 빈번한 I/O 발생은 overhead를 발생시킨다.
실행되는 process의 수가 많고, 빈번한 컨텍스트 스위칭이 발생한다면, 이는 성능을 떨어뜨린다.
하지만 I/O가 발생할 때, CPU는 비싼 자원을 기다리게 할 수 없다. 
I/O가 발생할 때, CPU를 게속 사용하려면 컨텍스트 스위칭은 피할 수 없다.

 

기본적인 Context switching은 위와 같고 mysql은 멀티스레드 구조라 아래와 같은 환경이다.

 

thread context-switching

 

스레드 스위치와 프로세스 스위치의 차이는 스레드 스위치 중에는 가상 메모리 공간이 그대로 유지되지만 프로세스 스위치에서는 그렇지 않다는 것이다. 

두 유형 모두 컨텍스트 스위치를 수행하기 위해 운영 체제 커널에 제어 권한을 넘겨주는 것을 포함한다.
레지스터를 전환하는 비용과 함께 OS 커널을 안팎으로 전환하는 프로세스는 컨텍스트 스위치를 수행하는 데 가장 큰 고정 비용이다.

 

context switching을 알아야하는 이유는 mysql의 장애상황이 대부분이 이것과 관련되기 때문이다.

대부분은 상황은 Cost가 큰 실행계획 등으로 인해 실행엔진에서 I/O 핸들러가 Disk에 가서 데이터를 찾을텐데 이게 오래걸리다 보면 waiting을 기다리지 않고 CPU는 다른 thread를 실행하기 위해 context switching을 발생하는 것으로 보인다.

 

#컨텍스트 스위치도 구분이 있다?

MySQL의 경우 스레드를 실행중에

조만간 디스크 IO, 네트워크 IO, 뮤텍스 대기 또는 양보와 같은 일부 차단 작업을 수행해야 한다.

이 경우 실행이 다른 프로세스로 전환되며, 이를 자발적 컨텍스트 스위치라고 한다.
반면에, 프로세스/스레드는 할당된 CPU 시간(그리고 이제는 다른 작업을 실행해야 함)을 사용하였거나 높은 우선순위 작업을 실행해야 하기 때문에 스케줄러에 의해 선점될 필요가 있을 수 있다.
이것을 비자발적인 컨텍스트 스위치라고 한다.

--> 어떤 것이 더 좋은 상황인지 아직 잘 모르겠다. mysql의 context switching 상황에 대해 설명한 블로그의 글을 참조하여 일단 정리한다.

 

#mysql 뮤택스 모든경합이 context-switching?

MySQL에서 모든 경합이 컨텍스트 스위치로 이어지는 것은 아니라는 점에 유의할 필요가 있다.
InnoDB는 자체적인 뮤텍스와 RW-locks를 구현하는데, 이는 종종 자원이 이용 가능해지기를 기다리기 위해 "spin"을 시도한다. 이것은 컨텍스트 스위치를 하기보다는 CPU 시간을 직접적으로 소모한다.

 

 

참조:
https://teraphonia.tistory.com/802
https://jhnyang.tistory.com/33
https://jinnify.tistory.com/36
https://www2.cs.duke.edu/courses/spring01/cps110/slides/threadsync/sld004.htm
https://hwan-shell.tistory.com/197

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

#프로세스란?
자기 자신만의 주소공간을 갖는 독립적인 실행 프로그램
멀티프로세스 : 두 개 이상의 프로세스가 실행되는 것. 프로그램이 여러개 띄워져 있는 형식
멀티 태스킹 : 두 개 이상의 프로세스를 실행하여 일을 처리하는것

 

#스레드란?
스레드(경량 프로세스) : 프로세스 내의 독립적인 순차 흐름 또는 제어
멀티 스레드 : 하나의 프로세스에서 여러 개의 스레드가 병행적으로 처리

 

mysql은 멀티 스레드구조로 standard 버전(무료)까지는 one-thread-per-connection 만 지원하며 enterprise 버전부터 thread-pool 을 사용할 수 있다.
MariaDB와 차이는 여기서 발생하는데 잔버그가 있어도 MariaDB를 사용하는 이유중 하나는 무료로 thread-pool을 쓸 수 있기 때문이다.
다시 mysql로 돌아와서 standard 버전을 기준으로 설명하면 한 커넥션당 하나의 스레드를 연다.
Thread Max값은 my.cnf에 max_connections 파라미터에 설정할 수 있고 이후 중요한 튜닝포인트 중 하나이다.

 

mysql>show global variables like 'thread%';

 

#mysql 조인방식

 

단일 코어에서 Nested Loop Join 처리
mysql에서는 모든 sql처리를 단일 코어에서 Nested Loop Join방식으로 처리를 한다. 기본적인 스토리지 엔진에서는 단일 코어 수행하고, 일부 3rd 스토리지 엔진을 플러그인으로 설치하면 병렬처리가 가능하다.

그래서 cpu 코어 개수를 늘리기 보다는, 단위 처리량이 좋은 cpu로 바꾸는게 더 유리하다

 

#Nested Loop Join(이하 NL-Join)이란?

선행 테이블(A)의 조건 검색 결과 값 하나하나를 엑세스 하면서 연결할 테이블(B)에 대입하여 조인하는 방식이다.
즉, 연결할 테이블 수(조인의 개수)가 늘어날수록, 쿼리 효율이 기하급수적으로 떨어진다.
모든 조인은 NL-Join으로 처리한다.


for문 안에서 for문을 실행시키는 구조라고 보면된다.

mysql은 오라클처럼 힌트가 많은 것이 아니기 때문에 limit 라는 좋은 기능을 잘 사용하고

나머지는 join과 index 스캔을 잘 튜닝해서 사용하면 된다.

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

#mysql DB에서 swap 메모리를 쓴다는 것은?

innodb buffer pool에 할당된 메모리를 다쓰고 메모리가 부족할 때 일시적으로 swap 메모리(disk)를 사용한다.

 

#리눅스에서 swap 메모리설정

vi /etc/sysctl.conf
---------------------------------------------------
## swap setting
vm.swappiness=0

vm.swappiness=0 으로 하면 스왑메모리를 사용하지 않겠다는 것이지만 실제로 0으로 설정해도 mysql DB는 최소한의 스왑메모리를 사용한다. 메모리가 부족하면 DB가 죽을 수 있으므로 swappiness에 0으로 할당되어있다 하더라도 DB OS 자체를 보호하기위해 최소한의 swap 메모리를 사용하는 것으로 보인다.

 

#Swap 메모리 초기화 방법

리눅스에서 swap 메모리를 초기화하는 것은 아주 간단하다.

일시적 메모리 사용의 증가로 swap을 사용하게될 경우 메모리에 여유가 생겨도 swap 메모리는 자동으로 초기화되지 않는다.  이것을 수동으로 초기화하려면 아래의 명령을 입력하면 된다. 당연히 root 권한에서 실행해야 한다.
swapoff -a && swapon -a

 

swapoff 처리에 시간이 조금 오래 걸릴 수 있는데 서버가 멈춘 것은 아니므로 걱정하지 않아도 된다.

swap 메모리에서 필요한 부분을 물리 메모리로 옮기는 처리중인 것이다.

 

#실제 초기화 작업

개발DB는 32G로 되어있고 mysql 인스턴스가 2개 떠있다.
개발장비가 부족하여 어쩔수 없이 멀티 인스턴스로 띄워놓았고, 각각 innodb_buffer_pool 은 16G 할당해놓았다.
당연히 50~80%가 MAX인데 그 이상(100%)을 설정해놓았으니 스왑메모리를 사용하였고 요즘 들어 너무 느려서 스왑메모리 사용을 초기화하고 관련 내용을 정리한다.

[root@admin]# free -m
             total       used       free     shared    buffers     cached
Mem:         32079      28264       3815          1        392       6806
-/+ buffers/cache:      21065      11014
Swap:        15999       5048      10951
//스왑메모리를 5G정도 사용하고 있다.

[root@admin]# swapoff -a && swapon -a
swapoff: /dev/sda3: swapoff failed: 메모리를 할당할 수 없습니다

DB가 메모리를 쓰고 있어서 남는 메모리가 없어 메모리 할당을 할수 없다는 메시지다.
DB 인스턴스 하나를 죽이고 버퍼캐시를 초기화해서 메모리를 비운다.

 

service mysqld_multi stop 1 --password=xxxxxxxx
echo 3 > /proc/sys/vm/drop_caches

[root@admin]# swapoff -a && swapon -a
//오 된다~

 

#top 확인

top - 18:30:39 up 564 days,  2:46,  2 users,  load average: 2.28, 1.89, 1.48
Tasks: 618 total,   2 running, 616 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  4.1%sy,  0.0%ni, 95.8%id,  0.1%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  32849832k total, 21902092k used, 10947740k free,     8956k buffers
Swap:  4177092k total,  4177092k used,        0k free,    78480k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                     
30537 root      20   0  102m  628  544 D 91.6  0.0   5:12.30 swapoff                                                                                                      
 5525 root      20   0     0    0    0 S  1.0  0.0  80:24.23 kondemand/3                                                                                                  
 5528 root      20   0     0    0    0 S  1.0  0.0 195:01.54 kondemand/6                                                                                                  
   15 root      RT   0     0    0    0 S  0.7  0.0   0:20.01 migration/3                                                                                                  
 5540 root      20   0     0    0    0 S  0.7  0.0 195:24.81 kondemand/18
//음 뭔가 작업을 하고 있다. 프로세스는 100%까지 쓰는 것으로 보아 cpu는 1core를 사용하는 것을 알 수 있다.
//나머지 DB인스턴스에 영향을 미칠정도는 아닌 것을 알 수 있다.

#메모리 확인

...
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22070      10009          1         12        117
-/+ buffers/cache:      21940      10139
Swap:         3030       3030          0
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22089       9990          1         12        117
-/+ buffers/cache:      21959      10120
Swap:         3000       3000          0
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22091       9987          1         12        117
-/+ buffers/cache:      21962      10117
Swap:         2998       2998          0
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22093       9985          1         12        116
-/+ buffers/cache:      21964      10115
Swap:         2997       2997          0
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22228       9851          1         13        124
-/+ buffers/cache:      22090       9989
Swap:         2803       2803          0

// 5047M에서 swap메모리 사용을 계속 줄여서 0에 수렴하게 만든 후 다시 스왑메모리를 사용하도록 할당하는 작업을 하고 있다.
//근데 이게 생각보다 많이 느리다. SATA2에 5G정도사용했는데.. 30분이상 걸리고 있다.

...
..
.
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      28125       3953          1        165       3658
-/+ buffers/cache:      24301       7778
Swap:        15999          0      15999

//완료된 후에 위처럼 스왑메모리가 초기화 되었고 free가 15999(16G)로 초기화 되어 있는 것을 알 수 있다.

다시 DB 인스턴스를 띄운다.

service mysqld_multi start 1

기분탓인가? 툴에서 쿼리 반응속도가 아까보단 좀 낫다.

장비하나 사주면 좋을텐데.. innodb 버퍼풀을 줄이고 싶지만 조인 20개이상 걸린 해비 쿼리가 많아서 줄이면
성능테스트를 할 수가 없다..;
당분간 이걸로 버텨야할 듯하다.
끝~

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

Table B 컬럼추가

 

테이블의 데이터가 쌓이고 컬럼이 많거나 파티션 테이블의 경우는 한번 생성한 스키마에 인덱스를 걸거나 컬럼을 추가할 이슈가 생기면 상당히 까다로운 작업을 해야한다. 

--> 컬럼이 많은 것과 파티션 테이블 중 어느게 더 반영이슈가 있나 테스트 해본적이 있는데 컬럼 30개, 1억건 rows 파티션 테이블보다 컬럼 200개짜리 1000만 rows 테이블의 online DDL이 반영이 더 오래 걸린다.

간단한 DDL문이라고 생각하면 대규모 장애를 만날 수도 있다.
일단 수행시간이 오래걸리고 alter table ALGORITHM=INPLACE, LOCK=NONE; 을 쓸 수없기 때문에
지금은 모르겠지만 암튼 default인 mysql 5.7.19까지는 ALGORITHM=COPY로만 수행이 가능한 것이라.. 작업이 쉽지 않았다. 거기다 이 테이블은 트리거도 걸려있다...;;; 머리가 아프다.

 

상용서비스 반영 전에 적용방식을 테스트해본다.

 

1) Online DDL

mysql> alter table TESTDB.TB_xxxx
    -> add COLUMN xxxx_NO bigint(11) unsigned DEFAULT NULL COMMENT 'xxxxxx',
    -> add COLUMN xxx_xxx_NO bigint(11) unsigned DEFAULT NULL COMMENT 'zzzzzz'
    -> ;
Query OK, 0 rows affected (31 min 31.39 sec)
Records: 0  Duplicates: 0  Warnings: 0

Alter table add column 테스트 해보았는데 30분이상 걸렸다.

이대로 online 반영은 어려울 거 같다.

 

2) TABLE 교체

2-1) 컬럼추가할 테이블A와 동일한 테이블B를 하나 더 만든다 스키마 생성할 때 컬럼을 추가해서 만드는 것이다.
2-2) 이전 테이블A의 데이터를 테이블B에 insert 한 후 테이블을 교체한다.

use TESTDB;
Create table TB_xxxx_TMP
(
...
xxxx_NO bigint(11) unsigned DEFAULT NULL COMMENT 'xxxxxx',
xxx_xxx_NO bigint(11) unsigned DEFAULT NULL COMMENT 'zzzzzz',
  PRIMARY KEY (`xxx_NO`,`xxx_SEQ`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='xxxx이력'
;

INSERT INTO TESTDB.TB_xxxx_TMP
(xxxx_NO, xxx_xxx_NO 컬럼 2개를 제외한 이전 테이블A의 컬럼을 순서대로...)
select * from TESTDB.TB_xxxx;
commit;

Query OK, 8833879 rows affected (14 min 45.61 sec)
Records: 8833879  Duplicates: 0  Warnings: 0
// 테스트기는 약900만 상용서비스 DB는 1200만 정도 있음

rename table TB_xxxx_TMP to TB_xxxx_TMP2, TB_xxxx_TMP to TB_xxxx, TB_xxxx_TMP2 to TB_xxxx_TMP;
//테이블교체는 A->C, B->A, C->B 로 진행한다. 바로 교체보다 안전한 방식임

그래도 10분이상 걸린다...
테스트 해본바로는 insert into select ~ 는 select .. into values ~과는 달리 meta Lock이 걸리지 않았고
작업중에도 TB_xxxx에 insert와 select가 가능했다.
하지만 트랜젝션 격리수준이 default인 repeatable-read라서 안정성 측면에서 트리거를 끊고 작업하는 게 속편할 거 같다.  트리거에 덴적이 있어서 더 조심스러울 수도 있지만;;
사업이랑 협의해서 insert는 반영후 hist에 업데이트하고 update는 무시하기로 협의하였다.
고객트래픽이 몰리는 시간에 반영하긴 어렵고 9시 전에 작업하기로 하였다.

 

#최종작업계획 

2-1) insert update 트리거 중단
2-2) 컬럼추가할 테이블A와 동일한 테이블B를 하나 더 만든다 스키마 생성할 때 컬럼을 추가해서 만드는 것이다.
2-3) 이전 테이블A의 데이터를 테이블B에 insert 한 후 테이블을 교체한다.
2-4) insert update 트리거 재생성
2-5) TB_xxxx 가 트리거를 건 테이블의 pk 비교하여 추가된 부분을 insert함

#실제 반영리뷰
테스트기에서 작업은 14분정도 소요되었지만 실제 상용반영은 5분 29초였고
차이는 리눅스 OS와 mysql 버전은 5.7.19로 같은데, DISK가 테스트기는 SATA2이고 상용기는 SSD이다.
SSD가 3배정도 빠른 것을 알 수 있다..ㅎㅎ

2-5) pk 비교는 Left outer join 으로 하여 TB_xxxx가 null 인 것을 찾는 방식으로 작업하였고
수행시간은 30초정도 걸렸다 1000만건이 넘더라도 Pk의 b-tree 인덱스 성능은 강력하다는 것을 알 수 있다.
작업 끝~

 

#Thereafter

mysql 8.0.12 버전부터(MairDB 10.3.7 부터) Game changer가 등장했다.

상용버전을 mysql 8(=5.8) 로 올려야할 강력한 이유가 생겼다.

그것은 algorithm = instant 인데 Online DDL 을 지원한다.

DBA에게 축복과도 같은 기능으로 추후 Data dictionary 좀 더 공부하고 정리해서 올릴 예정..

반응형

'RDB > mysql' 카테고리의 다른 글

mysql을 시작하기 전에 1  (0) 2020.08.04
mysql swap 메모리 사용 초기화  (0) 2020.07.22
mysql order by 정렬 빼도 될까? 2  (0) 2020.06.26
mysql order by 정렬 빼도 될까? 1  (0) 2020.06.19
MODIFY vs CHANGE 차이  (0) 2020.05.27
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

정렬인덱스를 정리하던 중에 흥미로은 블로그의 글을 보고 한번더 확인해보기로 했다.
http://www.gurubee.net/lecture/2260

--> 내용을 요약하자면 복합인덱스를 선언하면 order by을 쓰지 않더라도 ASC정렬이 가능하다는 것이다.

 

일단 복합인덱스 (A,B,C)의 정렬 인덱스를 사용할 수 있는 경우의 수를 알아본다.

--> 기본적으로 인덱스는 equal 조건만 사용가능하고 like 검색시 value% 은 가능하지만 %value은 불가하다.

 

1) 인덱스 사용가능한 경우
#1-1) 정렬만 사용한경우
order by a,b
order by a,b,c
--> Using index condition

 

#1-2) where + 정렬 사용한 경우
where a = 1
order by b,c
--> Using index condition

where a = 1
and b = 2
order by c
--> Using index condition

 

# Using index condition란?
WHERE 절의 인덱스를 이용한 조건에서 체크 조건이 있을 경우 체크 조건 처리를 스토리지 엔진이 하도록 전달하는 것이다.


2) 인덱스 사용 불가능한 경우

#2-1) 정렬만 사용한경우
#a를 뺀 경우
order by b,c
-->복합인덱스는 첫번째 컬럼이 선언되지 않으면 사용x

 

#순서대로 아닐때
order by a,c --> Using where; Using temporary; Using filesort 정렬인덱스 사용x
order by a,b,c,d --> Using where; Using temporary; Using filesort 정렬인덱스 사용x

 

# desc 정렬사용할 때
order by a,b,c desc --> Using where; Using temporary; Using filesort 정렬인덱스 사용x

 

#2-2) where + 정렬 사용한 경우

where a = 1
order by a,c
--> a가 포함되어있으면 Using index condition; Using filesort

where절에 a만 인덱스 쓰는 거고 order by는 인덱스를 사용하지 못하고 재정렬하기 위해 filesort를 쓴다.

 

a가 없으면 Using where; Using temporary; Using filesort 정렬인덱스 사용x

 

#테스트

select
T1.SEQ_NO
,T2.APLY_DV_CD
,T1.STD_NO
,T1.APLY_DATE
,T1.REG_DATE
from TESTDB.TB_TSET_20200626 T1
inner join TESTDB.TB_TSET_20200626_TEL T2
ON T1.SEQ_NO =T2.SEQ_NO
where T1.STTS_CD ='93'
order by T1.STTS_CD,T1.APLY_DATE,T1.REG_DATE
;

 

 

#인덱스 생성전

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T2 ALL SEQ_NO       110865 100 Using where; Using temporary; Using filesort
1 SIMPLE T1 eq_ref PRIMARY PRIMARY 92 TESTDB.T2.SEQ_NO 1 10 Using where

 

#정렬을 위해 인덱스를 추가한다.

alter table TESTDB.TB_TSET_20200626
add index IDX_TSET_20200626_12 (STTS_CD,APLY_DATE,REG_DATE);

 

 

 

#인덱스 생성후

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T1 ref PRIMARY,IDX_TEST_20200626_12 IDX_TEST_20200626_12 8 const 7668 100 Using index condition
1 SIMPLE T2 ref SEQ_NO SEQ_NO 93 TESTDB.T1.SEQ_NO 1 100  

 

 

#정렬조건 변경
order by T1.STTS_CD,T1.APLY_DATE,T1.REG_DATE -->order by T1.APLY_DATE,T1.REG_DATE

where 절에 STTS_CD쓰고 order by 절에서 APLY_DATE, REG_DATE 만 선언해도 정렬이 가능한지 플랜과 실제데이터를 확인한다.

 

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T1 ref PRIMARY,IDX_TEST_20200626_12 IDX_TEST_20200626_12 8 const 7668 100 Using index condition
1 SIMPLE T2 ref SEQ_NO SEQ_NO 93 TESTDB.T1.SEQ_NO 1 100  

플랜 같고, 데이터 정렬도 같다.

 

 

#이제 블로그에서 얘기한대로 order by 자체를 빼고 STTS_CD만 where절에 걸었을 때도 정렬이 되는지 확인해본다.

select
T1.SEQ_NO
,T2.APLY_DV_CD
,T1.STD_NO
,T1.APLY_DATE
,T1.REG_DATE
from TESTDB.TB_TSET_20200626 T1
inner join TESTDB.TB_TSET_20200626_TEL T2
ON T1.SEQ_NO =T2.SEQ_NO
where T1.STTS_CD ='93'
#order by T1.APLY_DATE,T1.REG_DATE
;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T1 ref PRIMARY,IDX_TEST_20200626_12 IDX_TEST_20200626_12 8 const 7668 100  
1 SIMPLE T2 ref SEQ_NO SEQ_NO 93 TESTDB.T1.SEQ_NO 1 100  

일단 using index condition 없다. 하지만 IDX_TEST_20200626_12 인덱스를 썼다고는 한다. 데이터를 확인해본다.


흥미롭다. 실제로 order by를 쓰지 않았지만 데이터 정렬은 order by를 사용한 것과 동일하다.
이것이 가능한 이유는 IDX_TEST_20200626_12 인덱스를 생성할 때 STTS_CD,APLY_DATE,REG_DATE 순서대로 ASC 정렬을 해서 인덱스를 가지고 있고
STTS_CD만 사용하더라도 정렬된 인덱스의 값을 가져오기 때문에 나머지 컬럼은 선언하지 않더라도 정렬된 데이터를 확인할 수 있었다.

order by 정렬을 뺄 수 있다니..

 

 

정렬 인덱스는 뺄 수 있는 것을 확인했다. 이번엔 좀 다른 것을 테스트 해본다.

select
T1.SEQ_NO
,T2.APLY_DV_CD
,T1.STD_NO
,T1.APLY_DATE
,T1.REG_DATE
from TESTDB.TB_TSET_20200626 T1
inner join TESTDB.TB_TSET_20200626_TEL T2
ON T1.SEQ_NO =T2.SEQ_NO
where T1.STTS_CD ='4'
order by T1.APLY_DATE,T1.REG_DATE
;

# 정렬조건을 바꿔 T1.STTS_CD ='4'로 테스트한다.

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T2 ALL SEQ_NO       110865 100 Using where; Using temporary; Using filesort
1 SIMPLE T1 eq_ref PRIMARY,IDX_TEST_20200626_12 PRIMARY 92 TESTDB.T2.SEQ_NO 1 50 Using where

 

 

인덱스 있는데.. 안탄다. temporary에도 모자라서 filesort까지 쓴다.  수행시간도 2.9초나 걸린다.
옵티마이져가 멍청해졌나? 강제로 인덱스를 태워본다.

select
T1.SEQ_NO
,T2.APLY_DV_CD
,T1.STD_NO
,T1.APLY_DATE
,T1.REG_DATE
from TESTDB.TB_TSET_20200626 T1 force index (IDX_TEST_20200626_12)
inner join TESTDB.TB_TSET_20200626_TEL T2
ON T1.SEQ_NO =T2.SEQ_NO
where T1.STTS_CD ='4'
order by T1.APLY_DATE,T1.REG_DATE
;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T1 ref IDX_TEST_20200626_12 IDX_TEST_20200626_12 8 const 213603 100 Using index condition
1 SIMPLE T2 ref SEQ_NO SEQ_NO 93 TESTDB.T1.SEQ_NO 1 100  

인덱스는 타지만 row수를 보니 더 최악이다...  수행시간도 5초나 걸리고 옵티마이져는 나보다 똑똑했다.;;

 

 

#왜이런 결과가 나올까?

select
count(*)
from TESTDB.TB_TSET_20200626
where T1.STTS_CD ='4';
// 전체 row 439,947 건중에 418,328 가 STTS_CD = 4 이다. 
// 분포도 95% 
// 이러면 옵티마이져가 풀스캔이 유리하다고 판단하고 인덱스를 당연히 안쓴다.

 

 

#앞선 조건은 왜 인덱스스캔이 가능했을까?

select
count(*)
from TESTDB.TB_TSET_20200626
where T1.STTS_CD ='93';
//확인해보니 전체 row수 439,947건중에 STTS_CD = 93 은 4262건이다. 
//분포도 0.97% 

인덱스를 설정할 때 데이터 분포도의 중요성이 여기서 다시 한번 확인된다.
복합인덱스를 선언하더라도 첫번째 컬럼의 분포도가 낮다면 order by 정렬인덱스를 제대로 쓰지 못할 수 있고
인덱스 무효화 혹은 강제로 인덱스를 태우면 풀스캔보다 더 느려지는 현상이 발생한다는 것을 확인했다.
끝~

 

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

일하다보면 사업부서의 요청으로 통계정보를 추출해달라는 요청을 많이 받는다.
DBA가 없는 회사는 개발자들이 직접 작업하는 경우가 많고 이게 업무에 생각보다 많은 부분을 차지하고 있다.

 

SAS나 SAP이 있는 회사라면 그걸 쓰면 될텐데.. 이건 너무 비싸고 그렇다고
별도의 데이터 마트나 샌드박스가 구축은 시간이 오래걸리고 이것도 생각보다 비용이 많이 들어간다.
그리고 결정적으로 SAS 도입을 검토한다고 해서 회의에 참여한 적이 있는데 음.. SAS쪽  사업담당자의

시연을 보면서 들던 생각은  저 정도를 쓰려면 MS의 ACCESS 정도는 문제없이 사용할 정도의 숙련도를 가진 사업담당자야할 거 같고, 교육을 받더라도 이해하고 제대로 사용하기에는 많은 시간이 걸릴 거 같다는 생각이 들었다.

 

일단 여기도 그렇지만 결국 EXCEL이다.
엑셀은 row수가 98만 건이 넘어가면 쉘을 쪼개던지 아니면 파일을 분리해야 한다.
보통은 피벗이나 엑셀함수로 원하는 정보를 만들텐데.. 데이터가 커지면 무한정 루프를 돌면서 업무지연이 발생한다는 것을 알게되었다.

 

최대한 요구사항에 맞게 데이터를 가공해서 전달하고 Excel에서 쓰는 함수를 최소화할 수 있게 전달해 주길 원했고
초기 포멧의 row seq 정렬에 맞게 추출해서 주기를 원했다.
결국 Excel이나 DB나 순서정렬이 문제였고, 거기에 맞게 가공하기 위한 작업을 하려면
DB도 대용량 데이터의 JOIN과 order by를 사용해야하고 filesort와 데이터 사이즈가 커지면 temparory memory를 쓸 수 밖에 없다.

 

다른 것보다 filesort를 쓴다는 것은 옵티마이져가 스캔한 것을 다시 한번 메모리에 올리고 재정렬을 해야하므로 부하가 많이 가는 작업이고, 보통은 DB에 부하를 주지 않게하기위해 out file로 처리해서 데이터를 재가공해서 EXCEL로 전달했다. order by 를 사용하지 않고 정렬이 가능한지 여부를 구글링 해보았는데 대부분 답은 정합성을 보장할 수 없다는 얘기였고, 정말 사용할 수 없는지 테스트 해보기로 했다.

 

1) MyISAM 테이블 엔진 테스트
일단 테이블 생성하고 DB툴을 이용하여 엑셀이나 CSV 파일을 DB에 import 한다.

 

CREATE TABLE `TMP_LOAD_20200619` (
  `SEQ_NO` int(10) unsigned NOT NULL COMMENT '시퀀스',
  `ENTER_NO` varchar(12) NOT NULL COMMENT '가입 시퀀스',
  PRIMARY KEY (`SEQ_NO`),
  UNIQUE KEY IDX_LOAD_20200619_01 (`ENTER_NO`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


select * from TMP_LOAD_20200619;
1	500053900208
2	500053900209
3	500053900210
4	500053900218
5	500053900219
6	500053900220
7	500053900214
8	500053900215
9	500053900216
10	500053900217
...

//50만건 정도 올려보았는데 순서대로 insert 되는 것을 확인했다.
//select 역시 순서대로 나온다.

delete from TMP_LOAD_20200619
where SEQ_NO = 3;
commit;
// 시퀀스 3을 삭제해 보았다.

select * from TMP_LOAD_20200619;
1	500053900208
2	500053900209
4	500053900218
5	500053900219
6	500053900220
7	500053900214
8	500053900215
9	500053900216
10	500053900217
11	500053900228
...

insert into TMP_LOAD_20200619
VALUES (500001,'500053904383');
commit;
// 500001번째 시퀀스를 가진 데이터를 추가로 넣어보았다.

select * from TMP_LOAD_20200619;

1	500053900208
2	500053900209
500001	500053904383
4	500053900218
5	500053900219
6	500053900220
7	500053900214
8	500053900215
9	500053900216
10	500053900217
...

MyISAM 엔진의 특성이다.
초기 데이터에서 삭제하면 select하면 보이진 않지만 빈공간은 사라지지 않고 존재하고 새로 입력한 데이터가 거기에 들어간다. 이거를 다시 생각해보면 MyISAM은 한번 입력한 데이터의 정렬은 유지되며 신규로 update나 delete가 일어나지 않는이상 ASC의 정렬은 유지된다고 판단할 수 있다.

다시말하면 초기데이터가 유지된다면 order by 정렬을 사용하지 않아도 된다는 얘기다.

 

실제로 TMP_LOAD_20200619 테이블을 드라이빙 테이블로 Left Join을 여러개 수행하고 where 조건절로 끊어서 데이터를 가공해서 최종 order by 정렬을 사용한 것과 사용하지 않은 것을 beyond compare로 비교해 보았고
50만건이 100% 일치함을 확인했다.

 

MyISAM을 사용시 초기데이터가 바뀌지 않는다면 order by 정렬을 빼도 될거 같다.

 

2) InnoDB 테이블엔진 테스트

 

CREATE TABLE `TMP_LOAD_20200619` (
  `SEQ_NO` int(10) unsigned NOT NULL COMMENT '시퀀스',
  `ENTER_NO` varchar(12) NOT NULL COMMENT '가입 시퀀스',
  PRIMARY KEY (`SEQ_NO`),
  UNIQUE KEY IDX_LOAD_20200619_01 (`ENTER_NO`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


1	500053900208
2	500053900209
3	500053900210
21	500053900211
22	500053900212
23	500053900213
7	500053900214
8	500053900215
9	500053900216
10	500053900217
..
// 원하는 순서가 아니다.. 뭔가 다르다.

 

 

InnoDB는 데이터 정렬하는 방법이 다르다.
테이블의 데이터를 PK값으로 정렬되어 저장하고 pk의 값의 정렬은

실제 데이터에서 보듯이 내가 생각하는 seq_no 순서대로의 순서가 아니고 clustered index로 저장하여 정렬한다.

 

#클러스터링 인덱스(clustered index)란?
여러개의 인덱스를 하나로 묶는다는 뜻으로 테이블의 PK에 대해서만 적용되는 내용이다.

즉 PK값이 비슷한 레코드끼리 묶어서 저장하는 것을 클러스터링 인덱스라고 표현한다.
중요한 것은 PK값에 의해 레코드의 저장 위치가 결정된다는 것이고, 또한 프라이머리 키값이 변경된다면 그 레코드의 물리적인 저장 위치가 바뀌어야 한다는 것을 의미한다.

 

#결론

innodb의 PK정렬이 order by 비용을 줄여 빠르다고 하는데, 실제 테스트해 본바로는 select는 대용량 일수록 MyISAM의 순서정렬을 사용하지 않은 쿼리가 훨씬 빨랐다.

데이터 추출작업이 필요할 때 order by를 뺄수 있는 MyISAM 엔진을 적극활용하자.

끝~

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,

MODIFY vs CHANGE 차이

RDB/mysql 2020. 5. 27. 17:14
반응형

#사용방법

alter table 테이블이름 modify 현재컬럼이름 int(20) NOT NULL;
alter table 테이블이름 change 현재컬럼이름 변경할컬럼이름 int(20) NOT NULL;

 

MODIFY는 컬럼 타입을 변경하기 위해 쓰고 CHANGE 는 컬럼이름을 바꾸기 위해 쓴다.
근데 CHANGE로도 컬럼 타입만을 바꾸기도 한다.(이름은 똑같이 쓰고..)

 

ex)
alter table 테이블이름 change 현재컬럼이름 현재컬럼이름 int(30) NOT NULL;

 

그래서 성능테스트 해봤는데 큰 차이는 없는 듯하다.
데이터 타입이 다를경우 어떤 차이가 발생하는지 테스트해 보았지만 별차이 없었다.

 

다만 테스트 중 성능이슈를 보려고 pk의 컬럼사이즈를 왔다갔다 테스트해봤는데
상용 운영DB에서는 int(1) -> int(10) 가능하지만 int(10) -> int(1) 은 곤란하다.

 

CREATE TABLE `TMP_LOAD_20200511` (
  `SEQ_NO` bigint(20) unsigned SEQ_NOT NULL,
  `DENT_NM` varchar(12) SEQ_NOT NULL COMMENT '가입번호',
  PRIMARY KEY (`SEQ_NO`),
  KEY `IDX_LOAD_20200511_01` (`DENT_NM`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
;
--> 10만건 데이터 insert 


ALTER TABLE TMP_LOAD_20200511 MODIFY SEQ_NO bigint(30) unsigned NOT NULL; 
ALTER TABLE TMP_LOAD_20200511 CHANGE SEQ_NO SEQ_NO bigint(40) unsigned NOT NULL;
<-- 성능차이 없다.(0.05초)

ALTER TABLE TMP_LOAD_20200511 MODIFY SEQ_NO bigint(30) unsigned NOT NULL;
ALTER TABLE TMP_LOAD_20200511 CHANGE SEQ_NO SEQ_NO bigint(1) unsigned NOT NULL;
<-- 성능차이 없다.(0.05초)

ALTER TABLE TMP_LOAD_20200511 CHANGE SEQ_NO SEQ_NO varchar(10) NOT NULL; 
<-- 2.1초 bigint에서 varchar는 시간이 좀 걸린다.
ALTER TABLE TMP_LOAD_20200511 CHANGE SEQ_NO SEQ_NO bigint(1) unsigned NOT NULL; 
<-- 2.4초 varchar는 bigint 시간이 좀 걸린다.
ALTER TABLE TMP_LOAD_20200511 MODIFY SEQ_NO bigint(30) unsigned NOT NULL; 
<-- 성능차이 없다.(0.05초)

ALTER TABLE TMP_LOAD_20200511 MODIFY SEQ_NO varchar(10) NOT NULL;
<-- 2.1초 bigint에서 varchar는 시간이 좀 걸린다.


ALTER TABLE TMP_LOAD_20200511 MODIFY SEQ_NO varchar(1) NOT NULL;
Error occurred during SQL query execution
이유:
 SQL Error [1265] [01000]: Data truncated for column 'SEQ_NO' at row 2
 

 [admin@db ~]$ perror 1265
MySQL error code 1265 (WARN_DATA_TRUNCATED): Data truncated for column '%s' at row %lu
//에러한번 내주고 테스트 끝~

 

#결론

두개 성능차이 없고 컬럼 타입변경에는 MODIFY든 CHANGE 아무거나 쓰면 된다.

 

 

#PK가 다른 테이블의 FK일 떄 컬럼 변경 테스트(MODIFY, CHANGE 둘다 동일함... 그냥 착각이었음)

ex) TABLE A (pk)-> TABLE B (A pk를 FK로 갖음) 일때!

 

TABLE A의 PK의 컬럼타입 사이즈를 늘리는 것은 가능하지만 줄이면 아래와 같은 에러가 난다.

 

//TESTDB.TABLE_A 에 PK가 VARCHAR(20) 이고 TESTDB.TABLE_B 에서 A의 PK를 FK로 걸어놓았을 때!
ALTER TABLE TESTDB.TABLE_A MODIFY TEST_CD varchar(30) NOT NULL COMMENT '대분류코드';
--> pk 컬럼 사이즈 늘리기 가능

ALTER TABLE TESTDB.TABLE_A MODIFY TEST_CD varchar(10) NOT NULL COMMENT '대분류코드';
--> 에러
Error occurred during SQL query execution
이유:
 SQL Error [1833] [HY000]: Cannot change column 'TEST_CD': used in a foreign key constraint 'TABLE_B_ibfk_1' of table 'TESTDB.TABLE_B'
 --> TABLE_B의 FK는 아직 VARCHAR(20)이라서 발생
 
 //해결방법
 //체크 포인트를 끄고 반영후 -> 다시 켬
 set FOREIGN_KEY_CHECKS = 0;
 ALTER TABLE TESTDB.TABLE_A MODIFY TEST_CD varchar(10) NOT NULL COMMENT '대분류코드';
 --> 가능
 set FOREIGN_KEY_CHECKS = 1;
 
 
 
 

 

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

반영중 뜻밖의 에러를 만났다.

CREATE TABLE TMP_LOAD_20200521 (
VALUE1 varchar(30) NOT NULL,
VALUE2 varchar(30) NOT NULL,
VALUE3 varchar(200) NOT NULL,
VALUE4 varchar(8) DEFAULT NULL,
VALUE5 varchar(8) DEFAULT NULL,
VALUE6 varchar(4000) DEFAULT NULL,
VALUE7 varchar(1) NOT NULL DEFAULT 'Y',
VALUE8 varchar(1) NOT NULL DEFAULT 'N',
VALUE9 varchar(50) NOT NULL,
VALUE10 datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
VALUE11 varchar(50) NOT NULL,
VALUE12 datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (VALUE1 ,VALUE2),
KEY IDX_VALUE3_01 (VALUE3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='테스트';

//스키마 생성안되고 에러남
SQL Error [1067] [42000]: Invalid default value for 'VALUE10'

 

mysql 5.7에서 개발하던 테이블 스키마를 mysql 5.5에 넣던 중에 발생햇는데..

음 왜 5.7을 5.5에 넣어야만 했는지는 사정이있고.. 여하튼 이게 안되네;

 

하위버전으로 마이그레이션 한 경우는 별로 없어서 케이스를 찾기 힘들었고

버전 올려라, 걍 datetime 써라 라는 얘기 밖에..

그러던 중에 이런 얘기를 찾을 수 있었다.

 

--------------------------------------------------------------------------------------

datetime 컬럼에 기본값이 now() 적용은 안되고 current_timestamp 를 사용해야함
mysql 5.5 버전 이하에서는 한 테이블당 1개의 컬럼만 current_timestamp를 사용할 수 있다.

mysql 5.6부터 이 제한이 해제되었다.

---------------------------------------------------------------------------------------

 

설마라고 생각하고 테스트를 진행했다.

일단 datetime은 timestamp로 바꾸고 not null 보다는 default로 넣으라는 얘기가 많아서 그대로 수정함

CREATE TABLE TMP_LOAD_20200521 (
VALUE1 varchar(30) NOT NULL,
VALUE2 varchar(30) NOT NULL,
VALUE3 varchar(200) NOT NULL,
VALUE4 varchar(8) DEFAULT NULL,
VALUE5 varchar(8) DEFAULT NULL,
VALUE6 varchar(4000) DEFAULT NULL,
VALUE7 varchar(1) NOT NULL DEFAULT 'Y',
VALUE8 varchar(1) NOT NULL DEFAULT 'N',
VALUE9 varchar(50) NOT NULL,
VALUE10 timestamp DEFAULT CURRENT_TIMESTAMP,
VALUE11 varchar(50) NOT NULL,
VALUE12 timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (VALUE1 ,VALUE2),
KEY IDX_VALUE3_01 (VALUE3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='테스트';

SQL Error [1293] [HY000]: Incorrect table definition; 
there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause
// only one...설마

CREATE TABLE TMP_LOAD_20200521 (
VALUE1 varchar(30) NOT NULL,
VALUE2 varchar(30) NOT NULL,
VALUE3 varchar(200) NOT NULL,
VALUE4 varchar(8) DEFAULT NULL,
VALUE5 varchar(8) DEFAULT NULL,
VALUE6 varchar(4000) DEFAULT NULL,
VALUE7 varchar(1) NOT NULL DEFAULT 'Y',
VALUE8 varchar(1) NOT NULL DEFAULT 'N',
VALUE9 varchar(50) NOT NULL,
VALUE10 timestamp DEFAULT CURRENT_TIMESTAMP,
VALUE11 varchar(50) NOT NULL,
VALUE12 datetime NOT NULL,
PRIMARY KEY (VALUE1 ,VALUE2),
KEY IDX_VALUE3_01 (VALUE3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='테스트';

// 된다..헐;;

진짜 mysql 5.5는 CURRENT_TIMESTAMP 는 컬럼에 하나만 지원한다. ㅎㅎ

하위버전으로 마이그레이션하는 것은 어렵다는 것을 한번 더 확인했다.

끝~

 

 

 

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,