- 소트하거나 추가 저장공간이 필요할 때 Oracle 은 PGA(인메모리), temp 테이블 스페이스(디스크.)를 활용한다.
- 소트 머지 조인(양쪽 집합을 미리 소트하고 저장.) 해시 조인 (해시 테이블 저장).
- 추가로 필요하다면 temp 테이블 스페이스 활용.
- 소트, 데이터 그루핑을 할 때도 마찬가지.
- 5.1.1. 소트 과정
- 소트를 하는 과정에서 Sort Area 만 사용해서 완료할 수 있는지 여부에 따라 2가지 종류로 나뉜다.
- 메모리 소트, Internal Sort : PGA 의 Sort Area 만 사용해서 소트하는 경우.
- 디스크 소트, External Sort : 용량이 부족해서 디스크까지 사용해서 소트하는 경우.
- 디스크 소트는 디스크 I/O 가 발생해서 성능저하가 발생하기도 하지만, 부분범위 처리가 불가능하게 함으로써 성능저하가 일어나는 부분도 매우 크다.
- 소트를 하는 과정에서 Sort Area 만 사용해서 완료할 수 있는지 여부에 따라 2가지 종류로 나뉜다.
5.1.2. 소트 오퍼레이션.
소트 연산이 사용되는 경우에 대해 알아보자.
- Sort Aggregate
- 전체 행을 대상으로 집계를 수행. Sort Area 를 사용함.
- Sort Order by
- 데이터를 정렬할 때.
- Sort Group by
- group by 후 집계 시에 sort 알고리즘 사용 (메모지). 결과 보장 x
- Sort Unique
- 서브 쿼리에 대해 unnest 되는 경우 && unique 조건이 없는 경우 (4.4절 참고)
- Union 과 같은 집합 연산을 수행하는 경우
- Distinct 연산자를 사용하는 경우 (+order by. ) (특정 시점 이후로는 order by가 없다면 hash unique 사용.)
- Sort Join
- 소트 머지 조인을 사용하는 경우.
- Window sort
- 전체 행에 대해 분석함수를 수행하는 경우.
5.2. 소트가 발생하지 않도록 SQL 작성
소트는 메모리 집약적, cpu 집약적인 연산이기 때문에 disk IO 가 발생하면 더욱 큰 성능 저하가 일어난다.
5.2.1. Union VS Union All
- Union 은 소팅 후에 중복이 발생하는지 여부를 모두 확인해야하지만, union all 은 모든 레코드를 다 합친다. (중복 확인이 없기 때문에 성능이 더 좋다. )
- 조회하는 데이터에 대해 높은 이해도를 가지고, 굳이 중복되지 않는 두 집합을 union 하는 일이 없도록 하는 것이 좋고, union all 을 쓰면서도 데이터 중복이 일어나지 않도록 조회하는 쿼리를 사용하면 좋다.
- 예시
원래 sql
- 예시
select 결제번호, 결제수단코드, 주문 번호, 결제금액, 결제일자, 주문일자 ...
from 결제
where 결제일자 ='20180316'
UNION
select 결제번호, 결제수단코드, 주문 번호, 결제금액, 결제일자, 주문일자 ...
from 결제
where 결제일자 <> '20180316'
중복 없이 union
select 결제번호, 결제수단코드, 주문 번호, 결제금액, 결제일자, 주문일자 ...
from 결제
where 결제일자 ='20180316'
UNION ALL
select 결제번호, 결제수단코드, 주문 번호, 결제금액, 결제일자, 주문일자 ...
from 결제
where 주문일자 = '20180316' and 결제일자 <> '20180316'
주문일자에 null 이 가능하다면, 다음 조건으로
and (결제일자 <> '20180316' and 결제일자 is null)
또는
and LNNVL(결제일자 ='20180316')
5.2.2. Exists 사용
- Distinct 연산자 대신에 사용.
- Distinct 연산자가 있으면 소팅 후 중복이 되지 않도록 연산이 수행됨. (Sort Unique: order by 절이 있는 경우.)
- distinct 대신 exists 문을 서브쿼리로 해서 넣으면, 부분범위처리도 되고 성능 상 이점이 생긴다.
- 예시
- 계약 X2 인덱스 구성이 「상품변호 + 계약일자」 -> 이때 execution plan 확인
select DISTINCT p.상품번호, p.상품명, p.상품가격
from 상품 p, 계약 c
where p.상품유형코드 = :pclscd
and c 상품번호 = p.상품번호
and c.계약일자 between :dt1 and :dt2
and c.계약구분코드 = :ctpcd
특정 계약 구분에 대해 특정 계약 기간 내에 거래가 이루어진 특정 유형 상품의 정보.
해당 상품에 대한 중복 없는 데이터.
- 실행계획
만약 이렇게 바꾼다면?
select p.상품번호, p.상품명, p.상품가격
from 상품 p
where p.상품유형코드 = :pclscd
and EXISTS (select 'x' from 계약 c
where c.상품번호 = p.상품번호
and c.계약일자 between :dt1 and :dt2
and c.계약구분코드 = :ctpcd)
-> Exists 쿼리는 서브쿼리에서 데이터의 존재여부만을 따질 뿐, 조건에 해당하는 데이터를 모두 읽을 필요가 없다.
상품에 여러 계약이 묶여있을 수 있지만, exists 조건에 의해 하나라도 성립하는 데이터가 있다면 그 데이터만 확인하고 넘어감.
-> 상품에 대한 데이터만 조회한다면 굳이 distinct 조건이 필요한게 아니라 exists 조건에 의해 존재여부만 해당하면 그 데이터를 조회하는 것만으로도 중복을 피할 수 있다.
특정 계약 구분에 대해 특정 계약 기간 내에 원하는 상품 유형의 거래가 있다면 -> 그 상품.
- 데이터의 존재 유무만 따지게 됨 -> (4.4.2절 참고)
- 계약 일자에 대해 부분범위 처리 가능.
5.3. 인덱스를 이용한 소트 연산 생략
- 인덱스가 키, 칼럼순으로 정렬된 상태를 유지한다는 점을 이용하면,
- order by, group by 절이 있을 때 소트 생략이 가능하다.
- Top N 쿼리 특성을 결합하면, 온라인 트랜잭션 시스템에서 대량 데이터를 조회할 때 매우 빠른 응답속도를 낼 수 있다.
5.3.1. Sort Order By 생략
select 거래일시, 체결건수, 체결수량, 거래대금
from 종목거래
where 종목코드 = 'KR123456'
order by 거래일시
인덱스가 종목코드 + 거래일시로 구성되지 않은 경우. 소트 연산을 수행할 수 밖에 없고,
- 매번 테이블 엑세스로 데이터 레코드를 조회해 온 뒤, 정렬을 수행하게 된다.
인덱스가 종목코드 + 거래일시로 구성된 경우, 소트 연산을 생략 가능하다.
- 결과를 바로 반환 가능.
- 부분 범위 처리도 가능.
**부분범위처리를 이용한 튜닝 기법, 아직도 유효한가?
특정 개수만 먼저 반환하고, 다음 클라이언트 요청이 왔을 때 남은 데이터를 반환하는 식의 부분범위 처리는,
클라이언트와 바로 연결된 2Tier 구조에서만 가능해보인다.
3 Tier 아키텍처란, WAS, AP 서버가 중간에 더 존재하는 아키텍처인데,
서버 리소스를 수많은 클라이언트가 공유하는 구조이므로, 클라이언트가 커넥션을 독점할 수 없고 단위 작업을 마치면 커넥션을 반환해야한다. 따라서 어디까지 반환했는지 등의 정보를 유지할 수가 없기 때문에, 부분범위처리가 불가능해보인다.
하지만 실제로는 이 기능이 가능한데, Top N 쿼리 덕분에 가능하다.
5.3.2. Top N 쿼리
전체 레코드 중 상위 N개만 선택해서 반환하는 쿼리
언어 별 형태.
- SQL Server
select TOP 10 거래일시, 체결건수, 체결수량, 거래대금 from 종목거래 where 종목코드 = 'KR123456' and 거래일시 >= '20180304' order by 거래일시
- Oracle
select * from ( select 거래일시, 체결건수, 체결수량, 거래대금 from 종목거래 where 종목코드 = 'KR123456', and 거래일시 >= '20180304' order by 거래일시 ) where rownum <= 10
- SQL Server
형태만 보면, 전체 레코드를 조회한 다음, 정렬을 수행해서 상위의 N 개 쿼리를 취하는 형태로 보인다.
- 전부 조회 한 다음, 정렬 후 rownum 부여.
그런데 굳이 전체 정렬하지 않아도 된다는 걸 눈치채고 동작하는 알고리즘!
인덱스를 이용해서 소트가 생략된 구조에서는, 읽다가 N개를 읽고나면 멈추는 구조가 되기 때문에 성능상 매우 빠른 구조가 된다.
- 인덱스 존재 시, 인덱스를 타고 정렬 순서대로 스캔
- 상위 N개만 도달하면, STOP!
- 정렬은 필요 최소한만 진행됨
- 실행계획
- Sort Order by 절 대신 COUNT(STOP KEY) 라는 절이 보인다.
- rownum 으로 지정한 건 수만큼 읽으면 데이터를 더 이상 읽지 않겠다는 의미가 된다.
- 이를 이하 Top N StopKey 알고리즘이라고 부르자.
- 페이징 처리
- 3 Tier 환경에서 부분범위처리를 적용할만한 부분.
- 예시
- 페이지당 데이터는 10개씩 보여주는 예시.
- page = 1 : 1
10, 2 : 1120 - page = 3이면, 21번째~30번째 데이터를 보여줘야함.
select * from (select rownum no, a.* from ( select 거래일시, 체결건수, 체결수량, 거래대금 from 종목거래 where 종목코드 = 'KR123456' and 거래일시 >= '20180304' order by 거래일시 ) a where rownum <= (:page * 10) ) where no >= (:page - 1)* 10 + 1
이 때의 실행 계획
만약, rownum 조건을 다음과 같이 수정한다면?
select *
from (select rownum no , a.*
from
(
select 거래일시, 체결건수, 체결수량, 거래대금
from 종목거래
where 종목코드 = 'KR123456'
and 거래일시 >= '20180304'
order by 거래일시
) a
where no between (:page - 1)* 10 + 1 and (:page * 10)
바꾸면 이렇게 실행되어버린다.
소트 생략이 가능한 인덱스이므로 sort 는 여전히 실행되지 않아도 되지만, 도중에 멈추지 않는 것이다.
(전체 데이터에 대해서 조회하고 rownum no 로 부여한 후에, 필터링을 수행하게 됨. )
-> 페이징 처리 안티패턴임.
- (실제로 이렇게 paging 쿼리를 작성해주는 프레임워크도 존재한다. )
- rownum 이 사용된 조건 절이 바로 Top N StopKey 알고리즘이 실행되기 위한 조건!
- 트레이스 확인 대신 더 쉽게 확인할 수 있는 방법
- 조회 조건에 해당하는 데이터를 아주 많이 입력해둔다.
- 조회하는 SQL 을 수행한다.
- 첫번째 페이지는 출력되었지만, 컴퓨터에 귀를 가져다대면 하드웨어가 돌아가는 소리가 계속난다.
- 다 처리했음에도 불구하고, 해당 between 조건에 해당하는 다른 데이터가 존재하는지 확인하려고 계속 조회하기 때문에 나는 소리.
부분범위 처리가 가능하도록 SQL 구성하기 - 튜닝 사례
부분범위 처리가 가능하게 SQL 을 구성한다는 의미
- 인덱스를 활용가능하게 조건절을 구사.
- 조인을 사용한다면 NL 조인으로 (작은 테이블의 경우 Hash Join 의 Build Input 으로 가도 가능. )
- Order By 절이 있어도 소트 연산 생략이 가능하게끔.
예시
- 거래 테이블
- 인덱스 구성
- 거래_PK: 거래일자 + 계좌번호 + 거래순번
- 거래_XOR: 계좌번호 + 거래순번 + 결제구분코드
select * from ( select 계좌번호, 거래순번, 주문금액, 주문수량, 결제구분코드 , 주문매체구분코드 from 거래 where 거래일자 = :ord_dt order by 계좌번호, 거래순번, 결제구분코드 ) where rownum <= 50
인덱스를 이용한 소트연산 생략이 일어날 수 없다. 50개만 조회하면되는데도 전부 조회하고, 정렬하고, 상단 50개만 가져오게 되는 것.
- 결제 구분 코드가 없었으면 가능했겠지만...(거래_PK )
- 거래_XOR 의 선두 칼럼이 거래일자였다면 가능했겠지만...
데이터 모델에 대한 이해와 집합적 사고를 해보면, 거래_PK 를 사용한 경우
특정 거래 일자에 대해 계좌번호 + 거래 순번으로 정렬한다면 중복이 없고 알아서 잘 정리된 상태일 것.
따라서 굳이 결제 구분코드를 뒤에 두고 정렬할 필요가 없다.
결제구분코드를 order by 조건절에서 제거하면, sort order by 실행계획도 사라지고 부분범위처리도 잘 일어난다.
5.3.3. 최대값/최솟값 구하기
- MIN / MAX 값을 구하는 경우, Sort Aggregation 이 일어남
- 마지막에 데이터 정렬을 수행하는 건 아니지만, 전체 데이터를 읽으면서 값을 비교한다.
- 특히 인덱스를 사용한다면, 왼쪽 혹은 오른쪽만 타고가서 값을 구할 수 있다.
- 인덱스를 사용하는 경우
- 왼쪽만 타고 가거나(MIN) 오른쪽만 타고가면(MAX) 구할 수 있다.
- Oracle 기준으로 8i 버전 이후로 다음과 같은 실행계획이 가능해졌다. (이전에는 전체 행을 조회했었음. )
- 원래 전체행을 조회하는 경우
- 인덱스만 타고 가는 경우
- 인덱스를 이용해서 MIN/MAX 를 구하는 조건
- 예시
- DEPTNO + MGR 十 SAL 순으로구성
CREATE INDEX EMP_X1 ON EMP (DEPTNO , MGR, SAL); SELECT MAX (SAL) FROM EMP WHERE DEPTNO = 30 AND MGR = 7698;
-> FIRST ROW : 조건에 맞는 레코드 하나를 찾았을 때 탐색을 멈춘다는 의미.
-> 이를 'FIRST ROW StopKey 알고리즘'으로 부르자.
스캔 과정
DEPTNO=30 인 경우인 데이터의 가장 오른쪽부터 조회하면, MGR+SAL 값이 최대인 경우에 해당한다. 거기서 MGR 이 '7698'인 첫번째 레코드를 만나면 바로 그게 찾던 레코드가 된다.
- DEPTNO는 엑세스 조건, MGR 는 필터 조건이 된 상황.
인덱스가 SAL + DEPTNO + MGR로 구성된 경우라면?
Index Range Scan 은 안되지만 Index Full Scan.
가장 오른쪽에서부터 탐색을 시작해서, DEPTNO=30, MGR=7698인 첫번째 레코드를 찾으면 탐색을 종료하면 됨.
-> 조건절 칼럼과 MAX 칼럼이 모두 인덱스에 구성된 경우.
그렇지 않은 경우라면?
인덱스가 DEPTNO + SAL 로만 구성된 경우
CREATE INDEX EMP_X1 ON EMP (DEPTNO, SAL );
SELECT MAX(SAL) FROM EMP WHERE DEPTNO = 30 AND MGR = 7698;
-> DEP_NO = 30 인 전체 레코드를 읽어서 테이블에서 MGR이 7698인 조건을 필터링 한 후, MAX(SAL)을 구한다.
즉, FIRST ROW StopKey 알고리즘이 실행되지 않았다.
**TOP N 쿼리 이용해서 최소/최대값 구하기
같은 조건에서도 TOP N 쿼리를 사용하도록 하면 더 좋은 성능을 보일 수 있다.
CREATE INDEX EMP_X1 ON EMP (DEPTNO, SAL);
SELECT *
FROM ( SELECT SAL
FROM EMP
WHERE DEPTNO = 30
AND MGR = 7698
ORDER BY SAl DESC
)
WHERE ROWNUM <= 1;
-> 위의 MIN/MAX 쿼리보다는 더 좋은 성능을 보인다.
DEPT_NO 에 해당하는 모든 테이블을 읽는 것이 아니라 DEPT_NO=30 에 해당하는 레코드 사이에서 가장 오른쪽부터 탐색하면서 MGR=7698인 레코드를 찾으면 바로 멈춘다.
5.3.4. 이력 조회
이력을 관리하는 테이블의 경우.
이력을 조회할 때, First Row Stopkey 알고리즘 혹은 Top N Stopkey 알고리즘이 수행될 수 있게 인덱스를 설계하고 sql 을 작성하는 것이 고급 sql 튜너가 되기 위해 반드시 정복해야할 산이다.
같이 산을 정복해봅시다 ㅎㅎ
SELECT 장비번호, 장비명, 상태코드
, (SELECT MAX(변경일자)
FROM 상태변경이력
WHERE 장비변호 = P.장비번호) 최종변경일자
FROM 장비 P
WHERE 장비구분코드 = 'A001'
장비구분코드가 'A001' 인 데이터 중 상태코드가 변경된 가장 최신날짜를 상태변경이력 테이블에서 MAX(변경일자)로 조회한뒤 '최종변경일자'로 매핑하고 있다.
FIRST ROW Stopkey 알고리즘이 작동하는 것을 볼 수 있다.
상태변경이력_PK 의 구성이 장비번호, 변경일자, 변경순번 으로 구성.
만약 최종 변경 순번까지 알고 싶다면?
많은 개발자들이 작성하는 쿼리는 다음과 같다.
SELECT 장비번호, 장비명, 상태코드
, SUBSTR(최종이력, 1, 8) 최종변경일자
, TO_NUMBER(SUBSTR (최종이력, 9, 4)) 최종변경순번
FROM (
SELECT 장비번호, 장비명, 상태코드
, (SELECT MAX(H.변경일자 || LPAD(H.변경순번, 4))
FROM 상태변경이력 H
WHERE 장비번호 = P.장비번호 ) 최종이력
FROM 장비 P
WHERE 장비구분코드 = 'A001'
이때 실행계획
- 인덱스 칼럼을 가공 -> First Row Stopkey 알고리즘이 사용되지 않았다.
- 장비별 상태변경이력이 많다면, 문제가 될 수 있다.
SELECT 장비번호, 장비명, 상태코드
, (SELECT MAX(H.변경일자)
FROM 상태변경이력 H
WHERE 장비번호 = P.장비번호) 최종변경일자
, (SELECT MAX(H. 변경순번)
FROM 상태변경이력 H
WHERE 장비변호 = P . 장 비번 호
AND 변경일자 = (SELECT MAX(H.변경일자)
FROM 상태변경이력 H
WHERE 장비번호 = P. 장비변호)) 최종변경순번
FROM 장비 P
WHERE 장비 구분코드 = 'A001'
- 상태 변경 이력 테이블을 2번 순회하지만 First Row Stopkey 알고리즘이 작동하므로, 성능은 비교적 좋다.
- 하지만 조회하고 싶은 칼럼의 수가 많아진다면 쿼리가 매우 복잡해질 것.
단순하게 쿼리를 사용하면서도 성능을 올리기 위해서 튜너들이 사용해온 전통적인 방식
index_desc 힌트, rownum <= 1 조건
SELECT 장비번호, 장비명, 상태코드 , SUBSTR(최종이력, 1, 8) 최종변경일자 , TO_NUMBER(SUBSTR (최종이력, 9, 4)) 최종변경순번 , SUBSTR(최종이력, 13) 최종상태코드 FROM ( SELECT 장비번호, 장비명, , (SELECT /*+ INDEX_DESC(X 상태변경이력_PK) */ 변경일자 || LPAD(변경순번, 4) || 상태코드 FROM 상태변경이력 X WHERE 장비번호 = P.장비번호 AND ROWNUM <= 1 ) 최종이력 FROM 장비 P WHERE 장비구분코드 = 'A001' )
아까는 최종변경이력을 변경일자+변경순번만 조회했었는데
지금은 변경일자+변경순번+상태코드까지 합침 , ROWNUM <=1 조건
- 인덱스 구성이 잘 되어있어야 함. (장비번호 + 변경일자 + 변경순번)
- index 를 거꾸로 읽다가 조건에 부합하면 바로 조회하는 방식.
쿼리를 이렇게 사용했던 이유.
이런 느낌의 쿼리가 파싱 오류가 발생하기 때문에.
SELECT 장비번호, 장비명,
, SUBSTR(최종이력, 1, 8) 최종변경일자
, TO_NUMBER(SUBSTR(최종이력, 9, 4)) 최종변경순번
, SUBSTR(최종이력, 13) 최종상태코드
FROM (
SELECT 장비번호, 장비명
, (SELECT 변경일자 || LPAD (변경순번, 4) || 상태코드
FROM (SELECT 변경일자 , 변경순번 , 상태코드
FROM 상태변경이력
WHERE 장비번호 = P.장비번호 -- ORA-00904 (부적합한 식별자) 오류
ORDER BY 변경일자 DESC , 변경순번 DESC)
WHERE ROWNUM <= 1) 최종이력
FROM 장비 P
WHERE 장비구분코드 = 'A001'
- 변경일자, 변경 순번을 기준으로 desc order 한 후에 rownum <= 1을 수행하고 싶은데,
- 파싱 오류 발생
- 메인 쿼리 컬럼을 서브 쿼리 내 인라인 뷰에서 참조했기 때문에 발생
- 12c 버전부터는 오류 없이 실행된다.
11g 버전에서는 이렇게 실행했었음.
```sql
SELECT 장비번호, 장비명,
, SUBSTR(최종이력, 1, 8) 최종변경일자
, TO_NUMBER(SUBSTR(최종이력, 9, 4)) 최종변경순번
, SUBSTR(최종이력, 13) 최종상태코드
FROM (
SELECT 장비번호, 장비명
, (SELECT 변경일자 || LPAD (변경순번, 4) || 상태코드
FROM (SELECT 장비번호, 변경일자, 변경순번, 상태코드
FROM 상태변경이력
ORDER BY 변경일자 DESC , 변경순번 DESC)
WHERE 장비번호 P.장비번호
AND ROWNUM <= 1) 최종이력
FROM 장비 P
WHERE 장비구분코드 = 'A001'
- 변경일자, 변경순번을 기준으로 정렬을 수행한 후에, 장비번호가 맞는 지 필터링하고 rownum <=1 조건을 처리할 것 같지만 실제로는 그렇지 않다.
- Predicate Pushing’이라고 불리는 쿼리 변환이 작동한 결과로, WHERE 장비번호 P.장비번호 조건이 인라인 뷰 안쪽으로 들어간다.
- 12c 버전
SELECT 장비번호, 장비명, SUBSTR(최종이력, 1, 8) 최종변경일자 ,TO_NUMBER (S UBSTR ( 최종이력 , 9, 4)) 최종변경순번 ,SUBSTR(최종이력, 13) 최종상태코드 FROM ( SELECT 장비번호, 장비명 ,( SELECT 변경일자 || LPAD (변경순번, 4) || 상태코드 FROM (SELECT 변경일자, 변경순번, 상태코드 FROM 상태변경이력 WHERE 장비번호 = P.장비번호 ORDER BY 변경일자 DESC, 변경순번 DESC) WHERE ROWNUM <= 1) 최종이력 FROM 장비 P WHERE 장비구분코드 = 'A001' )

**상황에 따라 달라져야하는 이력 조회 패턴**
본 절의 주제가 ‘인덱스를 이용한 소트 연산 생략’이므로 ‘First Row Stopkey’ 또는 ‘Top N Stopkey’ 알고리즘이 작동할 수 있게 SQL 을 작성하는 내용 중심으로 살펴봤지만, 상황에 따라 이력 조회 패턴도 달라야 한다.
- 특히,전체(또는 상당히 많은) 장비의 이력을 조회할 때는 인덱스를 이용한 Stopkey 기능 작동여부가 튜닝의 핵심요소가 아니다. 인덱스 활용 패턴은 랜덤 I/O 발생량만큼 성능도 비례해서 느려지므로 대량 데이터 조회할 때 결코 좋은 솔루션이 되지 못한다.
```sql
SELECT P.장비번호, P.장비명
, H.변경일자 AS 최종변경일자
, H.변경순번 AS 최종변경순번
, H.상태코드 AS 최종상태코드
FROM 장비 P
, (SELECT 장비번호, 변경일자, 변경순번, 상태코드
, ROW_NUMBER() OVER (PARTITION BY 장비번호
ORDER BY 변경일자 DESC, 변경순번 DESC) RNUM
FROM 상태변경이력) H
WHERE H.장비번호 = P.장비번호
AND H.RNUM = 1;
- ROW_NUMBER()
- 결과 집합 내에서 지정한 정렬 순서에 따라 각 행에 고유한 순번을 부여하는 함수
- 윈도우 함수이므로, GROUP BY처럼 집계해서 줄이는 게 아니라 원본 행 개수를 유지하면서 각 행에 순번을 붙입니다.
선분이력 맛보기
- 이런 식으로 상태변경이력을 관리하는 것도 가능하다.
- 다양한 이력모델과 조회 패턴의 장단점을 연구함으로써 각 상황에 맞는 최적 패턴을 선택할 수 있기를...
5.3.5. Sort Group By 생략
인덱스를 이용해 소트 연산을 생략할 수 있다는 사실은 많이 알려졌고 이해하기도 비교적 쉽다. 반면, 그룹핑 연산에도 인덱스를 활용할 수 있다는 사실은 모르는 분이 많다.
아래 SQL에 region 이 선두 컬럼인 인덱스를 이용하면 ,Sort Group By 연산을 생략할 수 있다.
select region, avg(age), count(*)
from customer
group by region
region 이 선두칼럼인 인덱스를 사용한다면, sort 도 생략 가능하고 부분범위처리도 가능해진다.
예시: ArraySize = 3일 때.
- 인덱스에서 ‘A’ 구간을 스캔하면서 테이블을 액세스하다가 ‘ B’를 만나는 순간, 그때까지 집계한 값을 운반단위에 저장한다.
- 계속해서 ‘B’ 구간을 스캔하다가 ‘ C’를 만나는 순간, 그때까지 집계한 값을 운반단위에 저장한다.
- 계속해서 ‘C’ 구간을 스캔하다가 ‘ D’를 만나는 순간, 그때까지 집계한 값을 운반단위에 저장한다, Array Size 가 3 이므로 지금까지 읽은 A, B, C에 대한 집계결과를 클라이언트에게 전송하고 다음 Fetch Call 이 올 때 까지 기다린다, (추가 Fetch Call 이 오지 않을 수도 있다. 그럴 경우,작업은 여기서 끝난다.)
- 클라이언트로부터 디음 Fetch Call 이 오면, 1 ~3 과정을 반복한다. 물론, 두 번째 Fetch Call 에서는 ‘D’ 구간부터 읽기 시작한다.
'개발 > TIL' 카테고리의 다른 글
RestController 의 응답 객체가 직렬화되는 방법 (1) | 2025.07.21 |
---|---|
aop 를 활용한 로깅 시스템 구축/ 보안 적용/ transaction 이해하기 (0) | 2025.05.14 |
[sql 튜닝] Chapter 4.3 해시 조인 4.4 서브쿼리 조인 (0) | 2025.05.08 |
[sql 튜닝 ]chapter 4.1 NL 조인, 4.2 소트 머지 조인 (0) | 2025.05.07 |
[sql 튜닝] 3.3 인덱스 스캔 효율화 (0) | 2025.04.18 |