DB 개발을 하다보면 가끔 특정대상의 최근정보를 가져와 할때가 있다.
또한 이런 경우 대상에 대한 데이터(row)가 unique 하지 않은경우가 있다.
다음의 경우는 위와같은 상황에서의 최근정보를 위한 정렬필드 선정에 따른 실행 계획이다.
checkpoint
dbcc dropcleanbuffers
dbcc freeproccache
go
set statistics profile on
set statistics io on
go
다른컨텐츠 및 연관시스템들의 구성으로 환경은 아래와 같이 고정돼있다.
필드 : Seq, UserGuid, ... ModifyTime
인덱스: UserGuid(Non Unique), Seq (PK, desc 가정)
case 1) 최근정보라 해서 ModifyTime(최근변경된시간)으로 정렬시
SELECT TOP 1 * FROM dbo.A_TBL WITH (NOLOCK) WHERE UserGuid = 118923
ORDER BY ModifyTime DESC
GO
Rows Executes StmtText
1 1 |--Sort(TOP 1, ORDER BY:([A_TBL].[ModifyTime] DESC))
30 1 |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([M_XXXXX].[dbo].[A_TBL]))
30 1 |--Index Seek(OBJECT:([M_XXXXX].[dbo].[A_TBL].[NIX_UserGuid]), SEEK:([A_TBL].[UserGuid]=118923) ORDERED FORWARD)
DefinedValues:
|--[Bmk1000]
'A_TBL' 테이블. 스캔 수 1, 논리적 읽기 수 93, 물리적 읽기 수 0, 미리 읽기 수 0.
case 2) 순차적 일련번호로 최근정보
SELECT TOP 1 * FROM dbo.A_TBL WITH (NOLOCK) WHERE UserGuid = 118923
ORDER BY ModifySeq (생략가능)
GO
Rows Executes StmtText
1 1 |--Top(1)
1 1 |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([M_XXXXX].[dbo].[A_TBL]))
1 1 |--Index Seek(OBJECT:([M_XXXXX].[dbo].[A_TBL].[NIX_UserGuid]), SEEK:([A_TBL].[UserGuid]=118923) ORDERED BACKWARD)
DefinedValues:
|--[Bmk1000], [A_TBL].[ModifySeq]
'A_TBL' 테이블. 스캔 수 1, 논리적 읽기 수 6, 물리적 읽기 수 0, 미리 읽기 수 0.
주위깊게봐야할 부분은 처리단계에 따른 Rows 이다.
ModifyTime필드 선정시 Index Seek 후 리프페이지에서 범위스캔후(partial scan) 30개의 로우를 힙에서 랜덤엑세스(lookup) 했다.
이 같은 결과는 (클러스터드인덱스가 존재하지 않는) 넌클러스터의 리프페이지에는 힙에 대한 주소값인 rowid만을 담고 있어.
ModifyTime의 top1 값을 알려면 일일이 찾아가 확인해야 하기때문이다.
case 1)

반면 ModifySeq필드 선정시 Index Seek 후 1 개의 로우를 Lookup 했다. Index Seek 후 리프페이지 에서 ModifySeq 의 Top1 만을 가지고 Lookup을 한다.
이는 클러스터인덱스가 있는 넌클러스터드 리프페이지에는 힙과는 달리 클러스터링키(ModifySeq)를 가지고있어 리프페이지에서 이미 ModifySeq top 1값을
알수있기 때문이다. 이 같은 이유로 리프페이지에서 ModifySeq top 1의 값만을 클러스터드인덱스에서 찾게된다.
물론 ModifyDate를 대체할수 있는 Seq 가 있어 제한된 환경이긴 하지만 어쨓든 처리범위 의 축소가
얼마나 중요한지 느끼게해주는 내용이다. 논리적 읽기수는 이부분에 차이가 나는것이다.
case 2)

set statistics profile off
set statistics io off
go