awk 커맨드란

awk programming language라는 프로그래밍 언어로 작성된 프로그램을 실행하는 커맨드.

어떤 파일들로부터 레코드(행)들을 읽어들여서 어떤 값을 조작하거나 데이터화하는 것을 목적으로 패턴 매칭 검사 등에 활용한다.

SQL 쿼리를 통해 내가 원하는 테이블의 row들을 하나씩 보면서 where 조건절에 부합하는 애들만 뽑아내는 것처럼, awk를 통해 특정 파일의 레코드들을 읽으며 비슷한 동작을 해줄 수가 있는 것이다.

 

간단한 활용 예시

다음과 같은 test.txt파일이 있다고 하자.

seq_num   name   age   score
1          kim    27     100
2          cho    26      99
3         park    24      94

 

awk를 통해 파일 전체를 출력하려면 다음과 같이 쓴다.

$ awk '{print}' test.txt
seq_num   name   age   score
1          kim    27     100
2          cho    26      99
3         park    24      94

 

awk를 통해 첫 번째 필드 값만 출력하려면 다음과 같이 쓴다.

$ awk '{print $1}' test.txt
seq_num
1
2
3

 

awk를 통해 score값(네 번째 필드)이 100점인 사람의 이름만 출력하려면 다음과 같이 쓴다.

$ awk '$4 == 100 {print $2}' test.txt
kim

 

 

awk 동작 원리

우선 awk는 입력 데이터(파일을)을 레코드와 필드라는 이름으로 구분한다. RDB 테이블의 행, 열을 떠올리면 이해하기 쉽다.

 

레코드의 텍스트들은 공백 문자(물론 다른 걸로 지정 가능)를 기준으로 각 필드를 구분한다.

레코드 하나하나는 $0값으로 들어오며, 해당 레코들의 각 필드들은 $1부터 채워지게 된다(즉 레코드별 첫 필드 = $1).

이렇게 각 레코드와 필드값들을 채웠으면, "pattern(일종의 조건)과 action(실제 수행할 내용)"을 통해 특정 동작들을 수행하게 된다.

 

awk 커맨드 구성은 다음과 같다.

 

$ awk [OPTION] 'pattern { action }' FILE_NAME(or vaiables)

 

옵션으로 다음 항목들을 사용 가능하다.

  • -F : 필드 구분자를 직접 지정한다
  • -f : awk program(awk 명령 스크립트) 이 있는 경로를 지정한다 (즉, 'pattern { action }' 부분은 -f가 없을 때 실행될 'awk program'에 해당
  • -v : awk program에서 사용될 특정 변수값들을 지정한다

 

pattern과 action은 둘 다 생략이 가능한 부분이다. pattern이 생략되면 모든 레코드가 적용되고, action이 생략되면 default action인 print가 수행된다. 

# pattern을 생략하는 경우
$ awk '{ print }' test.txt      # test.txt의 모든 레코드를 출력함
seq_num   name   age   score
1          kim    27     100
2          cho    26      99
3         park    24      94

# action을 생략하는 경우
$ awk '$4 < 100' ./file.txt     # test.txt에서 4번째 필드값이 100보다 작은 레코드들 출력함
2          cho    26      99
3         park    24      94

 

 

pattern

앞서 봤듯, awk에서 특정 조건에 따라 작업을 처리하게 하는 구성 요소. 크게 세 가지 유형으로 나눌 수 있으며, 이들을 조합하거나 단독으로 사용하여 데이터를 필터링하고 조작할 수 있다.

 

1. 관계식 패턴

$ awk '관계식패턴 {action}' FILE

관계식(비교 연산)을 사용하여 조건에 맞는 라인을 선택하게 한 뒤 action을 수행한다. 필드 값이 특정 조건을 만족하는지 여부를 평가할 때 등에 사용한다.

 

# ex 1
$ awk '$4 == 100 {print $2}' test.txt
kim

# ex 2
$ awk '$4 < 100' ./file.txt
2          cho    26      99
3         park    24      94

 

2. 정규표현식 패턴

$ awk '/패턴/ {action}' FILE

파일에서 pattern(정규표현식을 활용한)이 포함된 모든 라인을 찾아 해당 라인에 대해 action을 수행한다. 정규표현식은 /들로 둘러싸야 한다.

 

# ex 1 : k가 들어있는 레코드만 프린트
$ awk '/k/ {print}' test.txt
1          kim    27     100
3         park    24      94

 

 

3. BEGIN & AND 패턴

$ awk 'BEGIN {action1} pattern {action2} END {action3}' FILE

특수 패턴으로, BEGIN은 입력 데이터로부터 첫 번째 레코드를 처리하기 전 자신에게 지정된 action을 실행하고, END는 모든 레코드를 처리한 다음 자신에게 지정된 action을 실행한다.

 

# ex 1
$ awk 'BEGIN {print "Start Processing"} {print $1} END {print "End Processing"}' test.txt
Start Processing
seq_num
1
2
3
End Processing

 

패턴 범위 지정

$ awk 'start_pattern,end_pattern {action}' FILE

start패턴이 처음 나타난 레코드부터 end패턴이 마지막으로 나타난 라인까지의 모든 레코드들에 대해 action을 수행한다. 로그 파일의 특정 섹션을 추출할 때 유용하다.

 

 

action

특정 조건(pattern)에 일치하는 레코드에 대해 수행할 작업을 정의하는 부분이다. 중괄호 {} 안에 작성되며, 여러 가지 연산과 명령을 포함할 수 있다. 일반적으로 데이터 처리와 조작을 위해 사용한다.

 

편의를 위해, pattern은 생략하고 action들의 유형을 설명한다.

 

1. 출력

$ awk 'pattern {print}' FILE

패턴을 만족하는 레코드들을 출력한다.

 

# ex 1: 4번째 값이 200보다 작은 레코드들을 출력
$ awk '$4 < 200 {print}' test.txt
1          kim    27     100
2          cho    26      99
3         park    24      94

 

2. 변수 할당

$ awk 'pattern {sum = sum + 1}' FILE

pattern을 만족하는 레코드들에 대해 변수를 할당하는 동작을 한다. 

 

# ex 1 : 4번째 필드값이 200이하인 모든 레코드의 4번째 필드값을 합산 후 출력
$ awk 'BEGIN {sum_score = 0} $4 < 200 {sum_score = sum_score + $4} END {print sum_score}' test.txt
293

 

3. 조건문 사용

$ awk 'pattern {if(조건문) action }' FILE

pattern을 만족한 레코드들에 대해 조건문이 true로 평가되면 action을 수행한다. 참고로 else등도 활용 가능하다.

 

# ex 1: k가 포함된 레코드들에 대해 4번째 필드가 100이하면 해당 레코드를 출력한다
$ awk '/k/ {if($4 < 100) print $0}' test.txt
3         park    24      94

 

4. 반복문(for, while) 사용

$ awk 'pattern {반복문 action}' FILE

pattern을 만족한 레코드들에 대해 반복문을 돌며 action을 수행한다.

 

# ex 1 : 4번째 필드가 100보다 작은 레코드들에 대해, 1 ~ NF까지 루프를 돌며
# 현재 레코드 번호와 필드값을 출력
$ awk '$4 < 100 {for(i=1; i<=NF; i++) print NR, $i}' test.txt
3 2
3 cho
3 26
3 99
4 3
4 park
4 24
4 94

 

참고로, NR은 현재 레코드 번호(1부터 시작)를 의미하며 NF는 현재 레코드에 있는 필드값 수를 의미하는 것으로 내장 변수다.

또한 파이썬 등과 마찬가지로 break나 continue의 사용이 가능하다

 

 


이 외에도, next라는 action을 통해 다음 레코드로 넘기는 것도 가능하고(일종의 continue), exit를 통해 실행 중이던 awk를 종료시키는 것도 가능하다. 또한 sub, cos같은 다양한 내장 함수도 가지고 있다. 본 글에서처럼 파일에 작성된 값들에 대해 awk를 사용함으로써 데이터 변환 등이 가능하지만, vmstat이나 netstat등의 커맨드 결과를 파이프(|)를 통해 awk의 input으로 줌으로써 모니터링 관리 등에도 활용 가능하다. 

'Linux' 카테고리의 다른 글

리눅스에서의 사용자, 그룹 관리  (1) 2023.10.26

Subquery란

SQL 쿼리 내에서 또다른 SELECT절을 사용하는 문법을 쓸 때, 내부에 포함되는 SELECT절을 서브쿼리(Subquery)라 부른다. SELECT 절에서 사용되는 서브쿼리를 스칼라 서브쿼리(Scala Subquery), FROM절에서 사용되는 서브쿼리를 인라인뷰(Inline View), WHERE절에서 사용되는 서브쿼리를 중첩 서브쿼리(Nested Subquery)라고 부른다.

 

 

Scala Subquery란

SELECT절에서 사용되는 서브쿼리로, 반드시 하나의 record(행)을 리턴해야하며 그 행도 하나의 컬럼값만 가지고 있어야 한다. (즉 단일행, 단일 컬럼을 반환해야 한다)

 

예제로, 다음과 같은 두 개의 테이블이 있다고 하자. DEPARTMENTS는 회사에 있는 부서들의 부서id와 부서명을, EMPLOYEES는 임직원들의 이름과 소속 부서id 등을 가진다.

 

DEPARTMENTS 테이블

 

EMPLOYEES 테이블

 

이 때, 임직원들의 이름과 그들이 속한 부서의 이름을 함께 조회하고 싶다고 하고, 스칼라 서브쿼리를 사용해본다고 하자. 이때, 스칼라 서브쿼리의 FROM절에서 DEPARTMENTS를 참조하는가, EMPLOYEES를 참조하는가를 참조하는가를 주의해야 한다. 

 

스칼라 서브쿼리의 FROM절에서 DEPARTMENTS를 참조할 경우의 쿼리와 결과는 다음과 같다.

SELECT EMP_NAME, (
    SELECT DEPT_NAME
    FROM DEPARTMENTS d
    WHERE d.DEPT_ID = e.DEPT_ID) 
FROM 
    EMPLOYEES e;

WooHyung이 가지는 DEPT_ID = 5에 대한 레코드가 DEPARTMENTS테이블에는 없어, 해당 부분은 NULL로 표시됐다

 

 

스칼라 서브쿼리의 FROM절에서 EMPLOYEES를 참조할 경우의 쿼리와 결과는 다음과 같다.

SELECT (
        SELECT EMP_NAME
        FROM EMPLOYEES e
        WHERE d.DEPT_ID = e.DEPT_ID
    ), DEPT_NAME 
FROM 
    DEPARTMENTS d;

 

서브쿼리의 FROM절에서 DEPARTMENTS를 참조하는 경우, d.DEPT_ID = e.DEPT_ID를 만족하는 레코드가 하나만 나오기 때문에 문제가 되지 않는다. 하지만 서브쿼리의 FROM절에서 EMPLOYEES를 참조하는 경우, d.DEPT_ID = e.DEPT_ID를 만족하는 레코드가 다수가 나오기 때문에 문제가 되고, 위와 같은 오류가 생기는 것이다.

 

즉, 스칼라 서브쿼리는 반드시 하나의 레코드와 하나의 컬럼값을 리턴해야 한다.

 

 

스칼라 서브쿼리의  성능 측면에서의 문제점 & LEFT OUTER JOIN으로 변환

쿼리는 FROM(과 JOIN) → WHERE → GROUP BY → HAVING → SELECT → ORDER BY 순으로 실행된다. 즉 특정 테이블에서 특정 조건에 맞는 레코들들을 모두 뽑아오고, 해당 레코드들에 대해 select절에서 특정 컬럼들을 뽑아내는 식으로 동작한다.  스칼라 서브쿼리는 건수만큼 반복해서 수행된다. 데이터가 많아질수록 성능 저하의 주범이 될 수 있는 것이다.

 

따라서, LEFT OUTER JOIN과 Inline View를 활용하는 식으로 쿼리를 바꿔 성능을 개선시킬 수 있다. 다음 쿼리를 살펴보자.

SELECT DEPT_NAME, (
    SELECT COUNT(*)
    FROM EMPLOYEES e
    WHERE e.DEPT_ID = d.DEPT_ID
) NUMS
FROM DEPARTMENTS d;

 

부서별로 부서명과 소속 인원들의 이름을 보는, 스칼라 서브쿼리를 쓰는 쿼리다. 이를 LEFT OUTER JOIN과 Inline View을 활용하는 형태로 다음과 같이 바꿔쓸 수 있다.

SELECT DEPT_NAME, c.NUMS
FROM DEPARTMENTS d
LEFT OUTER JOIN (
    SELECT e.DEPT_ID, COUNT(*) AS NUMS
    FROM EMPLOYEES e
    GROUP BY e.DEPT_ID
) c
ON d.DEPT_ID = c.DEPT_ID;

 

기존에는 해당 서브쿼리가 건수만큼 반복돼서 수행됐지만, 이렇게하면 한 번만 수행되고 끝난다. 

 

하지만 늘상 스칼라 서브쿼리를 LEFT OUTER JOIN으로 바꾼다고 성능 향상이 되는 건 아니다(그러면 스칼라 서브쿼리 아무도 안 쓰지..). 상황별로 스칼라 서브쿼리를 쓰는게 이득일 때도 있다. 다만 스칼라 서브쿼리를 쓸 때는 속도를 고려하는 습관을 가지자.

 

 

 

다음과 같은 테이블이 있다고 하자.

SALES_HISTORY 테이블은 특정 상품들과 해당 상품들이 판매된 시간, 판매된 가격 정보를 가진다.

 

 

1. GROUP BY를 통한 집계

GROUP BY를 통해 특정 컬럼(들)을 기준으로 그룹핑한 다음 집계함수를 활용한 합이나 평균값의 산출이 가능하다. 본 글에서는 SUM을 활용한 상품들의 일자별 매출액을 조회하는 상황을 가정한다.

SELECT
  PRODUCT_ID,
  DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DAY,
  SUM(PRICE) AS TOTAL_SALES
FROM
  SALES_HISTORY
GROUP BY
  PRODUCT_ID, SALES_DAY;

 

각 상품들의 일자별 매출액을 조회

 

 

2. WITH ROLLUP을 가미한 집계

WITH ROLLUP을 가미하면, GROUP BY로 보여지는 결과물 뿐만 아니라 그룹핑한 각각의 소그룹별 집계와 전체 집계를 볼 수 있다.

SELECT
  PRODUCT_ID,
  DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DAY,
  SUM(PRICE) AS TOTAL_SALES
FROM
  SALES_HISTORY
GROUP BY
  PRODUCT_ID, SALES_DAY WITH ROLLUP;

 

각 상품들의 일자별 매출액과 더불어 상품별 매출액, 전체 매출액까지 조회

 

NULL값으로 표기된 부분들이 WITH ROLLUP을 통해 산출된 집계값이다. 1000번 상품 그룹에 대한 집계와 1001번 상품에 대한 집계, 1002번 상품에 대한 집계와 더불어 전체 집계 또한 나온 것을 확인할 수 있다. 

 

 

 

Persistence와 Persistence Framework

영속성은 프로그램이 종료되도 데이터가 보존되는 것을 말한다. 자바에서는 JDBC(Java Database Connectivity)라 불리는 기술을 통해 메모리에 있는 객체들에게 Persistence를 줄 수 있다(즉 데이터로 저장할 수 있다는 말).

 

 

자바 앱에서 JDBC API를 호출하고, 내부적으로 JDBC driver manager를 이용해 DB와 상호작용하는 것을 도식화한 그림이다. 참고로 MySQL, SingleStore 등 저마다의 DBMS들은 자신들에 맞는 JDBC driver를 제공한다.

 

그러나, 이렇게 JDBC만을 사용하는 것은 매번 JDBC로 DB와 커넥션을 맺고, 쿼리를 날려 수행하고, 결과를 받은 다음 커넥션을 끊는 작업을 프로그래밍해야 한다는 문제가 있었다. 이런 과정을 단순화하여 간단하게 DB와 상호작용하기 위해, 내부적으로 JDBC API를 활용하게끔 하여 개발자의 수고를 덜어주는 Persistence Framework가 등장했으며 대표적으로 SQL Mapper와 ORM이 있다.

 

 

SQL Mapper

개발자가 직접 작성한 SQL 쿼리의 실행 결과를 객체로 바인딩해주는 기술. 대표적으로는 MyBatis가 있다.

SQL에 의존성이 높은 방법으로, MyBatis의 경우 xml파일을 통해 SQL 쿼리를 별도 관리하며 동적 쿼리 작성(실행 중 사용자 입력을 통해 들어오는 파라미터 값에 따라 다른 쿼리가 실행되게 하는 것)을 통해 복잡한 쿼리도 처리할 수 있다는 장점이 있다. DBA같은 밥먹고 DB만 파오신 전문가 분들께 복잡한 쿼리 작성을 짬때리는게 가능해진다(?)는 장점도 있다. 그러나 어찌됐건 개발자가 직접 SQL을 작성해야 한다는 것에서 결국은 공수가 드는 것이며, 데이터 모델 패러다임 불일치(자바는 클래스와 객체를 통해 데이터를 모델링하나 DB는 테이블과 컬럼을 통해 데이터를 모델링한다는 것에서 오는 차이)가 발생한다는 단점이 있다. 즉, 객체지향적인 관점에서의 프로그래밍이 어렵다.

 

 

ORM (Object Relation Mapping)

객체와 DB의 데이터를 자동으로 매핑해주는 기술. 대표적으로는 JPA가 있다.

설정된 관계를 기반으로 자동으로 SQL 쿼리가 생성되며, DBMS에 의존적이지 않아 개발자가 비즈니스 로직에 좀 더 집중할 수 있는 효과를 줄 수 있다. 또한 DB 데이터를 객체로 매핑해주기 때문에 좀 더 객체지향적인 관점에서의 프로그래밍이 용이하다. 자체적으로 복잡한 쿼리의 자동생성은 어렵기 때문에 OLAP성 업무보다는 OLTP성 업무가 많이 쓰일 때 JPA를 활용하면 좋다.

 

참고로 JPA는 자바 진영에서 제공하는 ORM을 위한 표준 기술로 그 자체는 "인터페이스"다. 즉, JPA를 사용하려면 해당 인터페이스를 구현한 구현체를 별도로 사용해야 하며, 대표적으로 Hibernate가 가장 유명하다.

 

 

 

MySQL, 그리고 InnoDB의 Index

인덱스(Index)는 DB에서 테이블의 데이터를 빠르게 검색하기 위해 사용되는 보조적인 자료구조로, 인덱스와 실제 데이터는 따로 보관 및 관리된다. 전통적으로 인덱스를 위해 가장 많이 사용되는 자료구조는 B-tree (Balanced Tree)로, Leaf Node는 실제 데이터 레코드를 찾아갈 수 있는 주솟값을 가지고 있다.

 

B+ tree

 

MySQL 역시 B-tree 기반 인덱스를 사용하며, InnoDB 엔진의 경우 클러스터링 인덱스(클러스터링 테이블이라고도 부름)를 사용하는데 이는 테이블에서 PK(Primary Key)값이 비슷한 레코드들끼리 묶어 저장하는 걸 의미한다. 즉 스토리지 엔진으로 InnoDB를 사용한다면 아무 것도 하지 않아도 알아서 PK값들에 의해 B-tree 구조로 테이블의 데이터가 저장된다(각 Leaf Node가 레코드의 모든 컬럼 값을 가짐). 즉 PK값에 따라 레코드의 저장 위치가 결정되는 것이며, 만약 PK값이 변경된다면 해당 레코드가 저장되는 물리적 위치 또한 바뀌게 됨을 의미한다. 따라서 이렇게 클러스터링 인덱스로 저장되는 테이블은 PK 기반의 검색이 매우 빠르나 레코드의 저장이나 PK 변경은 상대적으로 느리다.

 

 

이 특성 때문에, 세컨더리 인덱스에 대한 B-tree를 기준으로 볼 때 스토리지 엔진으로 MyISAM을 사용할 경우 Leaf Node가 실제 레코드의 물리적인 주소들을 가지는 반면 InnoDB는 Leaf Node가 PK를 가진다. 즉 InnoDB를 사용할 경우 세컨더리 인덱스를 통해 데이터를 읽을 때 데이터를 바로 찾아가는 게 아니라, Leaf Node에 저장된 PK를 이용해 클러스터링 인덱스에서 찾아가게 된다.

 

본 포스트에서는 Index를 사용하지 않는 Full Table Scan방법과 Index를 사용하는 4가지 Scan 방법에 대해 다룬다.

 

Full Table Scan (=Table Full Scan)

말 그대로 테이블 전체 데이터를 순차적으로 scan하는 방식이다(배열에서 모든 데이터를 선형탐색으로 읽는 것에 비유할 수 있다). InnoDB가 데이터(인덱스도 포함)들을 저장하는 기본적인 단위는 "페이지"인데, Full Table Scan은 테이블의 데이터가 담긴 페이지들을 모두 읽어와서 읽게 된다. 레코드가 많은 테이블의 경우, Full Table Scan은 성능 저하의 주범이 되는 경우가 많다. 일반적으로 인덱스가 없거나, 또는 인덱스를 활용할 수 없거나, 아니면 테이블의 레코드가 너무 적어서 인덱스를 사용하지 않고 테이블 전체를 읽는 게 더 빠를 경우 이 방법이 사용된다.

출처 : 실무에 바로 쓰는 SQL 튜닝

 

Index Full Scan

인덱스의 Leaf 노드 전체(즉 인덱스의 처음부터 끝까지)를 순차적으로 scan하지만 실제 테이블을 참조하지는 않는 방식을 말한다. 쿼리가 인덱스에 명시된 컬럼들만으로 조건을 처리가능한 경우에 주로 이 방법이 사용된다.(B-tree의 각 노드가 인덱스로 명시된 컬럼값과 child node의 위치를 키-값 형태로 가지고 있음을 상기할 것) 인덱스 뿐만이 아닌 레코드까지 읽어야 한다면 절대 이 방식을 사용하지 않으며, 실제 테이블을 참조하지는 않으니 Full Table Scan보다는 효율적(왜냐면 인덱스의 전체 크기가 테이블 자체의 사이즈보다 작아서 Disk I/O가 줄어들기 때문)이다.

 

참고로 이렇게 쿼리가 인덱스에 존재하는 컬럼만으로 처리 가능할 때 "커버링 인덱스"라고 부른다.

 

출처 : 실무에 바로 쓰는 SQL 튜닝

 

Index Range Scan

특정 범위만큼의 인덱스를 스캔한 뒤 이에 대응하는 레코드들을 읽는 방식이다. 쿼리에서 BETWEEN이나 부등호 등이 사용되어 검색해야 할 인덱스의 범위(range)가 결정됐을 때 사용된다. 스캔의 시작점이 되는 리프 노드만 찾으면 그때부터는 종료 지점까지의 리프 노드들을 쫙 스캔한 후(B+ tree는 리프 노드끼리도 서로 연결돼있음을 상기), 필요하다면 읽은 리프 노드들이 가리키는 레코드들을 읽어온다(따라서 Random I/O가 수반되게 된다). 쿼리가 인덱스에 명시된 컬럼들만으로 조건을 처리가능하다면(즉 커버링 인덱스) Index Full Scan처럼 테이블 레코드들을 읽어오는 과정은 없다. 

출처 : 실무에 바로 쓰는 SQL 튜닝

 

Loose Index Scan 

인덱스에서 필요한 부분만 선택적으로 스캔한 뒤 이에 대응하는 레코드들을 읽는 방식이다. Index Range Scan과 비슷하게 동작하나, 필요한 인덱스 키와 불필요한 인덱스 키를 구분한 뒤 불필요한 인덱스 키는 무시하는 식으로 동작한다. 보통 GROUP BY, MAX, MIN 등이 사용된 쿼리를 최적화할 때 사용된다.

 

출처 : 실무에 바로 쓰는 SQL 튜닝

 

Index Skip Scan

인덱스는 두 개 이상의 컬럼에 대해서도 만들어줄 수 있는데(멀티 컬럼 인덱스 등으로 부름), 이 때 컬럼 순서에 따라 인덱스가 어떤 기준으로 정렬되는지가 결정된다.(예를 들어 A, B 순서로 인덱스를 만들었다면 A에 대해 먼저 정렬된 다음 B에 대해 정렬됨) 이때 WHERE절에 멀티 컬럼 인덱스의 첫 컬럼이 없으면 원래 인덱스를 사용할 수 없으나, 첫 컬럼이 없음에도 불구하고 인덱스를 사용할 수 있는 스캔 방식이 이 Index Skip Scan이다. 

 

예로, student테이블에 대해 다음과 같이 gender, address에 대해 인덱스가 걸려있다고 해보자.

 

ALTER TABLE students ADD INDEX ix_gender_address (gender, address);

 

이때다음 쿼리는 원래는 Full Table Scan 또는 Index Full Scan(커버링 인덱스인 경우)으로 수행될 것이다.

 

SELECT gender, address
FROM students
WHERE address LIKE "경기도%";

 

하지만 Index Skip Scan을 사용하면 WHERE절에서 쓰이지 않은 인덱스의 선행 컬럼에 대해 가능한 값들을 구한 다음, 해당 컬럼의 조건을 추가해 쿼리를 다시 실행하는 형태로 처리하게 된다. 즉 여기서는 WHERE절에서 쓰이지 않은 gender라는 컬럼에 대해, 해당 컬럼에서 유니크한 값들을 모두 뽑은 다음 다음과 같은 형태의 쿼리를 다시 실행하게 된다.

 

SELECT gender, address
FROM students
WHERE gender = "M" AND address LIKE "경기도%";

SELECT gender, address
FROM students
WHERE gender = "F" AND address LIKE "경기도%";

 

참고로, Index Skip Scan은 WHERE절에서 쓰이지 않는 선행컬럼의 유니크한 값의 개수가 적어야 하고(많으면 오히려 쿼리를 처리하는 속도가 더 느려질 수 있기 때문), 커버링 인덱스를 만족해야 한다는 단점이 있다.

+ Recent posts