인덱스란?
찾아보기가 있는 책은 주요 용어가 가나다순, 알파벳순으로 정렬돼 있고 용어 옆에 쪽수가 적혀 있어 해당 페이지를 펼치면 원하는 내용을 바로 찾을 수 있음 => 인덱스는 이와 같은 찾아보기와 비슷한 개념
주의❗️ 인덱스는 잘 사용해야지만 속도가 향상되고 시스템의 성능이 좋아집니다!
1) 클러스터형 인덱스
2) 보조 인덱스(=논클러스터형 인덱스=비클러스터형 인덱스)
기본키를 지정해봅시다!
CREATE TABLE TBL1
( a INT PRIMARY KEY,
b INT,
c INT
);
SHOW INDEX FROM TBL1;
SHOW INDEX FROM 테이블이름;
- key_name의 PRIMARY : 클러스터형 인덱스
- Non_unique : 0이면 고유 인덱스, 1이면 비고유 인덱스
1) 고유 인덱스 : 인덱스 값이 중복되지 않는 인덱스
2) 비고유 인덱스 : 인덱스 값이 중복되는 인덱스- Seq_in_index : 해당 열에 여러 개의 인덱스가 설정됐을 때 순서를 나타냄. 대부분 1
- Cardinality : 중복되지 않은 데이터의 개수
- Index_type : 인덱스가 어떤 형태로 구성되있는지 -> MySQL은 기본 B-Tree 구조
기본키와 UNIQUE 제약 조건을 설정해봅시다!
CREATE TABLE TBL2
( a INT PRIMARY KEY,
b INT UNIQUE,
c INT UNIQUE,
d INT
);
SHOW INDEX FROM TBL2;
다음으로는 기본키 없이 UNIQUE 제약 조건만 설정해봅시다!
CREATE TABLE TBL3
( a INT UNIQUE,
b INT UNIQUE,
c INT UNIQUE,
d INT
);
SHOW INDEX FROM TBL3;
UNIQUE 제약 조건을 설정한 열 중 하나에 클러스터형 인덱스를 생성해봅시다!
CREATE TABLE TBL4
( a INT UNIQUE NOT NULL,
b INT UNIQUE ,
c INT UNIQUE,
d INT
);
SHOW INDEX FROM TBL4;
a열에는 UNIQUE 제약조건에 NOT NULL을 설정하고, d열에는 기본키 설정해봅시다!
CREATE TABLE TBL5
( a INT UNIQUE NOT NULL,
b INT UNIQUE ,
c INT UNIQUE,
d INT PRIMARY KEY
);
SHOW INDEX FROM TBL5;
CREATE TABLE userTBL
( userID char(8) NOT NULL PRIMARY KEY,
userName varchar(10) NOT NULL,
birthYear int NOT NULL,
addr char(2) NOT NULL
);
INSERT INTO userTBL VALUES('YJS', '유재석', 1972, '서울');
INSERT INTO userTBL VALUES('KHD', '강호동', 1970, '경북');
INSERT INTO userTBL VALUES('KKJ', '김국진', 1965, '서울');
INSERT INTO userTBL VALUES('KYM', '김용만', 1967, '서울');
INSERT INTO userTBL VALUES('KJD', '김제동', 1974, '경남');
SELECT * FROM userTBL;
userId | userName | birthYear | addr |
---|---|---|---|
kHD | 강호동 | 1970 | 경북 |
KJD | 김제동 | 1974 | 경남 |
KKJ | 김국진 | 1965 | 서울 |
KYM | 김용만 | 1967 | 서울 |
YJS | 유재석 | 1972 | 서울 |
userId | userName | birthYear | addr |
---|---|---|---|
kHD | 강호동 | 1970 | 경북 |
KKJ | 김국진 | 1965 | 서울 |
KYM | 김용만 | 1967 | 서울 |
KJD | 김제동 | 1974 | 경남 |
YJS | 유재석 | 1972 | 서울 |
최종 결론
- PRIMARY KEY로 지정한 열에 클러스터형 인덱스 생성
- UNIQUE NOT NULL로 지정한 열에 클러스터형 인덱스 생성
- UNIQUE 또는 UNIQUE NULL로 지정한 열에 보조 인덱스 생성
- PRIMARY KEY와 UNIQUE NOT NULL이 같이 있으면 PRIMARY KEY로 지정한 열에 우선 클러스터형 인덱스 생성
- PRIMARY KEY로 지정한 열을 기준으로 데이터가 오름차순 정렬
B-Tree : 균형이 잡힌 트리
- 노드 : 트리구조에서 데이터가 존재하는 공간
- 루트노드 : 가장 상위에 있는 노드로 모든 출발은 이 노드에서!
- 리프노드 : 가장 말단에 있는 노드
- 중간 수준 노드 : 루트노드&리프노드 중간에 끼인 노드
B-Tree 구조가 아니라면 루트 페이지와 그 아래 연결은 존재하지 않고 리프 페이지만 있을 것. 이런 상황에서는 처음부터 검색하는 수밖에 없으므로 AAA~MMM 8개의 데이터를 검색하고 나야 결과를 얻을 수 있음
B-Tree 구조는 데이터를 검색할 때 매우 뛰어난 성능 발휘함. MMM 데이터를 검색하는 경우,
1) 먼저 루트 페이지를 검색. AAA, FFF, LLL 이라는 데이터 읽기
2) MMM은 LLL 이후에 나오므로 3번째 리프 페이지로 이동
3) 3번째 리프 페이지에서 LLL,MMM이라는 데이터 읽어 MMM 찾기
결국, 루트페이지에서 3건(AAA,FFF,LLL) + 리프페이지에서 2건(LLL,MMM) 검색
예시를 통해 알아봅시다!!
III 라는 데이터를 넣는다고 가정하면, JJJ가 한칸 이동하고 데이터 정렬
GGG라는 데이터를 넣는다고 가정하면, 두번째 리프 페이지에 빈 공간이 없기 때문에 페이지 분할 작업 일어납니다. MySQL은 비어 있는 페이지 하나 확보하고 두 번째 리프 페이지의 데이터를 공평하게 나눕니다.
이렇듯, 데이터를 1개만 추가했는데도 많은 작업이 일어납니다.
이번에는 PPP와 QQQ라는 데이터를 동시에 삽입한다고 가정하면,PPP는 네 번째 리프 페이지 맨 마지막에 추가. QQQ를 삽입하려면 페이지 분할 작업이 일어납니다. 페이지 분할 후 추가된 다섯번째 리프 페이지를 루트 페이지에 등록하려고 했지만 등록할 곳이 없어 분할이 필요합니다. 중간 페이지가 생성된 것입니다. QQQ를 삽입하기 위해서는 새로운 페이지 3개 생성, 2회의 페이지 분할 발생합니다.
결론 : 인덱스를 구성하면 데이터의 삽입, 수정, 삭제와 같은 변경 작업을 할 때는 성능이 나빠짐.
클러스터형 인덱스를 만들어봅시다!
CREATE TABLE clusterTBL
( userID char(8) ,
userName varchar(10)
);
INSERT INTO clusterTBL VALUES('YJS', '유재석');
INSERT INTO clusterTBL VALUES('KHD', '강호동');
INSERT INTO clusterTBL VALUES('KKJ', '김국진');
INSERT INTO clusterTBL VALUES('KYM', '김용만');
INSERT INTO clusterTBL VALUES('KJD', '김제동');
INSERT INTO clusterTBL VALUES('NHS', '남희석');
INSERT INTO clusterTBL VALUES('SDY', '신동엽');
INSERT INTO clusterTBL VALUES('LHJ', '이휘재');
INSERT INTO clusterTBL VALUES('LKK', '이경규');
INSERT INTO clusterTBL VALUES('PSH', '박수홍');
SELECT * FROM clusterTBL;
userId | userName |
---|---|
YJS | 유재석 |
kHD | 강호동 |
KKJ | 김국진 |
KYM | 김용만 |
KJD | 김제동 |
NHS | 남희석 |
SDY | 신동엽 |
LHJ | 이휘재 |
LKK | 이경규 |
PSH | 박수홍 |
위 테이블의 userID에 클러스터형 인덱스 구성해봅시다! userID를 기본키로 지정하면 클러스터형 인덱스가 자동 생성
ALTER TABLE clusterTBL
ADD CONSTRAINT PK_clusterTBL_userID
PRIMARY KEY (userID);
SELECT * FROM clusterTBL;
userId | userName |
---|---|
kHD | 강호동 |
KJD | 김제동 |
KKJ | 김국진 |
KYM | 김용만 |
LHJ | 이휘재 |
LKK | 이경규 |
NHS | 남희석 |
PSH | 박수홍 |
SDY | 신동엽 |
YJS | 유재석 |
클러스터형 인덱스 : 인덱스 페이지의 리프 페이지가 데이터 그 자체!!
다음은 보조인덱스를 만들어봅시다!
CREATE TABLE secondaryTBL
( userID char(8),
userName varchar(10)
);
INSERT INTO secondaryTBL VALUES('YJS', '유재석');
INSERT INTO secondaryTBL VALUES('KHD', '강호동');
INSERT INTO secondaryTBL VALUES('KKJ', '김국진');
INSERT INTO secondaryTBL VALUES('KYM', '김용만');
INSERT INTO secondaryTBL VALUES('KJD', '김제동');
INSERT INTO secondaryTBL VALUES('NHS', '남희석');
INSERT INTO secondaryTBL VALUES('SDY', '신동엽');
INSERT INTO secondaryTBL VALUES('LHJ', '이휘재');
INSERT INTO secondaryTBL VALUES('LKK', '이경규');
INSERT INTO secondaryTBL VALUES('PSH', '박수홍');
테이블 특정열에 UNIQUE 제약조건을 설정해 보조 인덱스 생성!
ALTER TABLE secondaryTBL
ADD CONSTRAINT UK_secondaryTBL_userID
UNIQUE (userID);
SELECT * FROM secondaryTBL;
입력한 순서와 같습니다.
userId | userName |
---|---|
YJS | 유재석 |
kHD | 강호동 |
KKJ | 김국진 |
KYM | 김용만 |
KJD | 김제동 |
NHS | 남희석 |
SDY | 신동엽 |
LHJ | 이휘재 |
LKK | 이경규 |
PSH | 박수홍 |
구조는 위 그림과 같습니다.
1) 검색의 경우
클러스터형 인덱스가 보조 인덱스보다 효율적입니다.
NHS(남희석)을 검색한다고 가정,
2) 삽입의 경우
보조 인덱스가 클러스터형 인덱스가 효율적입니다.
먼저, 클러스터형 인덱스를 살펴봅시다!
INSERT INTO clusterTBL VALUES('KKK', '크크크');
INSERT INTO clusterTBL VALUES('MMM', '마마무');
첫번째 리프페이지와 두번째 리프페이지에서 페이지 분할이 일어남. 2개 페이지가 추가로 생성되는 과정에서 많은 부하를 주어 속도 느려짐
다음은, 보조 인덱스를 살펴봅시다!
INSERT INTO secondaryTBL VALUES('KKK', '크크크');
INSERT INTO secondaryTBL VALUES('MMM', '마마무');
보조 인덱스는 데이터 페이지를 정렬하는 것이 아니므로 데이터 페이지의 뒤쪽 빈 부분에 데이터가 삽입. 인덱스의 리프 페이지에 약간의 순서 변경만 있고 페이지 분할은 일어나지 않음
인덱스 생성하는 방법
1) 테이블에 제약조건을 설정해 자동으로 생성
2) 인덱스를 생성하는 구문을 입력해 생성
DROP INDEX 인덱스 이름 ON 테이블이름;
SHOW INDEX FROM userTBL;
Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation |
---|---|---|---|---|---|
usertbl | 0 | PRIMARY | 1 | userID | A |
CREATE INDEX idx_userTBL_addr
ON userTBL (addr);
SHOW INDEX FROM userTBL;
Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation |
---|---|---|---|---|---|
usertbl | 0 | PRIMARY | 1 | userID | A |
usertbl | 1 | idx_userTBL_addr | 1 | addr | A |
CREATE UNIQUE INDEX idx_userTBL_userName
ON userTBL (userName);
SHOW INDEX FROM userTBL;
Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation |
---|---|---|---|---|---|
usertbl | 0 | PRIMARY | 1 | userID | A |
usertbl | 0 | idx_userTBL_userName | 1 | userName | A |
usertbl | 1 | idx_userTBL_addr | 1 | addr | A |
INSERT INTO userTBL VALUES('GHD', '강호동', 1988, '미국', NULL , NULL , 172, NULL);
Error Code : Duplicate entry '강호동' for key 'idx_userTBL_userName'
따라서 이렇게 이름이 중복되는 경우를 허용하지 않는다면 문제가 될 수 있기 때문에 고유 인덱스는 현재 중복되는 값이 없다고 무조건 설정하면 안됨.
절대 중복 허용하지 않는(주민등록번호,학번)등에만 UNIQUE 옵션 사용하기
1) DROP
DROP INDEX idx_userTBL_addr ON userTBL;
DROP INDEX idx_userTBL_userName_birthYear ON userTBL;
DROP INDEX idx_userTBL_mobile1 ON userTBL;
2) ALTER
ALTER TABLE userTBL DROP INDEX idx_userTBL_addr;
ALTER TABLE userTBL DROP INDEX idx_userTBL_userName_birthYear;
ALTER TABLE userTBL DROP INDEX idx_userTBL_mobile1;
ALTER TABLE userTBL DROP PRIMARY KEY;
SELECT userName, birthYear, add FROM userTBL WHERE userID='KHD'
sql문을 보면, 이름,출생연도 등에 인덱스를 생성해도 전혀 사용할일이 없음->WHERE절에 있는 userID에만 인덱스 생성할 필요가 있음중간 Question
질문 1
LIKE 검색 시 % 를 붙이게 되면, 인덱스를 참조하지 못하나요?
%단어%, %단어, 단어%
이유는 값이 무엇인지 모른다면 인덱스를 참조하는 것이 불가능하기 때문
테이블 풀 스캔이 진행됨질문 2
인덱스를 사용했을 때 효율이 좋은 기준은 무엇인가요?
전체 테이블의 15% 이내
대량의 데이터를 한 테이블에 저장할 때 그 내용을 물리적으로 별도의 테이블에 분리해서 저장하는 기법
몇 개의 파티션으로 분리되었든 사용자 입장에서는 하나의 테이블로 보이기 때문에 테이블 사용법은 동일.
MySQL 내부적으로 데이터가 분리되어 처리하기 때문에 시스템 성능 향상
EXAMPLE> 수십억건의 테이블에 쿼리를 수행한다고 가정,
MySQL은 최대 8192개의 파티션을 지원
CREATE DATABASE IF NOT EXISTS partDB;
USE partDB;
DROP TABLE IF EXISTS partTBL;
CREATE TABLE partTBL (
userID CHAR(8) NOT NULL, -- Primary Key로 지정하면 안됨
userName VARCHAR(10) NOT NULL,
birthYear INT NOT NULL,
addr CHAR(2) NOT NULL )
PARTITION BY RANGE(birthYear) (
PARTITION part1 VALUES LESS THAN (1970),
PARTITION part2 VALUES LESS THAN (1972),
PARTITION part3 VALUES LESS THAN MAXVALUE
);
part1 : 1970년 이전에 출생한 회원 저장
part2 : 1970-1971 출생한 회원 저장
part3 : 1972년 이후에 출생한 회원 저장
파티션 테이블에는 기본키를 설정하면 안됨!!
WHY?
파티션 테이블에 기본키를 설정하면 그 열로 정렬이 되기 때문에 기본키를 설정하면 안됩니다.
위의 정의된 파티션에 cookDB 데이터를 삽입하고 조회해봅니다.
INSERT INTO partTBL
SELECT userID, userName, birthYear, addr FROM cookDB.userTBL;
SELECT * FROM partTBL;
SELECT TABLE_SCHEMA, TABLE_NAME, PARTITION_NAME, PARTITION_ORDINAL_POSITION, TABLE_ROWS
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_NAME = 'parttbl';
SELECT * FROM partTBL WHERE birthYear < 1970 ;
위의 sql문으로 1970년 이전에 출생한 회원을 조회하게 된다면, 파티션2와 파티션3에는 접근조차하지 않고 효율적으로 조회하는 것이 가능합니다!
EXPLAN
SELECT * FROM partTBL WHERE birthYear < 1970 ;
동일한 sql문 앞에 EXPLAN 만 붙여주게 된다면 어떤 파티션에 접근했는지도 알려줍니다. 예상대로 part1 에만 접근했습니다.
1) 파티션을 재구성 해봅시다!
ALTER TABLE partTBL
REORGANIZE PARTITION part3 INTO (
PARTITION part3 VALUES LESS THAN (1974),
PARTITION part4 VALUES LESS THAN MAXVALUE
);
OPTIMIZE TABLE partTBL;
원래의 파티션 3을 1972~1974 와 1974 이후 로 분리하여 재구성하려고 한다면, 위와 같은 sql 문을 작성하면 됩니다.
분리 : ALTER TABLE~REORGANIZE PARTITION
재구성 : OPTIMIZE TABLE
2) 파티션을 합쳐 봅시다!
ALTER TABLE partTBL
REORGANIZE PARTITION part1, part2 INTO (
PARTITION part12 VALUES LESS THAN (1972)
);
OPTIMIZE TABLE partTBL;
원래의 파티션 1과 파티션 2를 합쳐 1972년 미만 파티션 12로 재구성해본다면,
결과는 위와 같습니다.
3) 파티션을 삭제해 봅시다!
ALTER TABLE partTBL DROP PARTITION part12;
OPTIMIZE TABLE partTBL;
SELECT * FROM partTBL;
파티션12를 삭제하게 되면 파티션과 함께 해당하는 데이터가 모두 삭제됩니다. 따라서 part3, part4 만 남게 됩니다.
인덱스와 파티션에 대한 공부를 마무리하며,,
최종 정리
인덱스와 파티션 모두 속도와 성능향상
💡 차이점?
인덱스 : 인덱스란 추가적인 쓰기 작업과 저장 공간을 활용하여 데이터베이스 테이블의 검색 속도를 향상시키기 위한 자료구조
파티셔닝 : 데이터베이스 분할 또는 파티셔닝은 데이터베이스를 여러 부분으로 분할하는 튜닝기법
- 하나의 테이블이 너무 커서 인덱스의 크기가 물리적인 메모리보다 훨씬 크거나, 데이터 특성상 주기적인 삭제 작업이 필요한 경우 등이 파티션이 필요
- 파티션은 데이터와 인덱스를 조각화해서 물리적 메모리를 효율적으로 사용할 수 있게 만들어 줌
- 테이블 파티셔닝에서 사용하는 인덱스의 종류
참고링크
리뷰 피드백
%단어%, %단어, 단어%
이유는 값이 무엇인지 모른다면 인덱스를 참조하는 것이 불가능하기 때문
(%단어%, %단어 -> 인덱스 참조 불가능 값을 모르기 때문 / 단어% -> 인덱스 참조 가능 처음값을 알기 때문)