수백만 행이있는 좁은 테이블에서 쿼리 성능을 향상시킬 수 있습니까?


14

현재 완료하는 데 평균 2500ms가 걸리는 쿼리가 있습니다. 내 테이블은 매우 좁지 만 4,400 만 행이 있습니다. 성능을 향상시키기 위해 어떤 옵션이 필요합니까?

쿼리

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'; 

탁자

CREATE TABLE [dbo].[Heartbeats](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [DeviceID] [int] NOT NULL,
    [IsPUp] [bit] NOT NULL,
    [IsWebUp] [bit] NOT NULL,
    [IsPingUp] [bit] NOT NULL,
    [DateEntered] [datetime] NOT NULL,
 CONSTRAINT [PK_Heartbeats] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

색인

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

색인을 추가하면 도움이됩니까? 그렇다면 어떤 모습일까요? 쿼리는 가끔씩 만 실행되기 때문에 현재 성능이 허용되지만 학습 연습으로 궁금합니다. 더 빨리 할 수있는 방법이 있습니까?

최신 정보

강제 색인 힌트를 사용하도록 쿼리를 변경하면 쿼리가 50ms 내에 실행됩니다.

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats] WITH(INDEX(CommonQueryIndex))
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 

올바른 선택적 DeviceID 절을 추가하면 50ms 범위에 도달합니다.

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' AND DeviceID = 4;

ORDER BY [DateEntered], [DeviceID]원래 쿼리에 추가 하면 50ms 범위에 있습니다.

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

이들은 모두 내가 기대했던 색인 (CommonQueryIndex)을 사용하므로 내 질문이 있다고 생각합니다.이 색인을 이와 같은 쿼리에 사용하도록 강요 할 수 있습니까? 아니면 내 테이블의 크기가 옵티 마이저를 너무 많이 ORDER BY버려서 또는 힌트를 사용해야 합니까?


"DateEntered"에 클러스터되지 않은 인덱스를 하나 더 추가하여 성능을 어느 정도 향상시킬 수 있습니다
Praveen

@Praveen 기본적으로 기존 인덱스와 동일합니까? 같은 필드에 두 개의 인덱스가 있기 때문에 특별한 작업이 필요합니까?
Nate

@ Nate, 테이블이 하트 비트라고하고 44million 레코드가 관련되어 있기 때문에이 테이블에 무거운 삽입물이 있다고 가정합니까? 인덱싱을 사용하면 커버링 인덱스 만 추가하여 속도를 높일 수 있습니다. 그러나 언급했듯이 때때로이 쿼리를 사용하면 무거운 인서트를 사용하는 경우 강력히 권장합니다. 기본적으로 인서트 하중이 두 배가됩니다. Enterprise Edition에서 실행 중입니까?
Edward Dortland

NC 색인에 deviceID가있는 것으로 나타났습니다. where 절에 포함시킬 수 있습니까? 그러면 결과 세트가 임계 값 아래로 내려 갑니까? <35k 레코드 (최고 1000 절 제외).
Edward Dortland

1
마지막 질문, 항상 날짜순으로 삽입하고 있습니까? 또는 장치가 서로 비동기로 삽입 될 수 있으므로 순서가 잘못 될 수 있습니다. 클러스터 된 인덱스를 DateEntered 열로 변경하려고 할 수 있습니다. 클러스터형 인덱스의 탈퇴 페이지는 이제 445 페이지입니다. int에서 datetime으로 가면 두 배가됩니다. 그러나이 경우에는 나쁘지 않을 수 있습니다.
Edward Dortland

답변:


13

옵티마이 저가 첫 번째 인덱스를 사용하지 않는 이유 :

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

[DateEntered] 열의 선택 문제입니다.

테이블에 4,400 만 개의 행이 있다고합니다. 행 크기는 다음과 같습니다.

ID의 경우 4 바이트, 장치 ID의 경우 4 바이트, 날짜의 경우 8 바이트, 4 비트 열의 경우 1 바이트. (태그, 널 비트 맵, var col 오프셋, 열 카운트)의 총 17 바이트 + 7 바이트 오버 헤드는 행당 총 24 바이트입니다.

140k 페이지로 제대로 번역됩니다. 4 천 4 백만 행을 저장합니다.

이제 옵티마이 저는 두 가지 작업을 수행 할 수 있습니다.

  1. 테이블을 스캔 할 수 있습니다 (클러스터 된 인덱스 스캔)
  2. 또는 색인을 사용할 수도 있습니다. 인덱스의 모든 행에 대해 클러스터형 인덱스에서 책갈피 조회를 수행해야합니다.

이제 특정 시점에서 비 클러스터형 인덱스에있는 모든 인덱스 항목에 대해 클러스터형 인덱스에서 이러한 모든 단일 조회를 수행하는 것이 비용이 더 많이 듭니다. 이에 대한 임계 값은 일반적으로 총 조회 수가 총 테이블 페이지 수의 25 % tot 33 %를 초과해야한다는 것입니다.

따라서이 경우 : 140k / 25 % = 35000 행 140k / 33 % = 46666 행.

(@RBarryYoung, 35k는 전체 행의 0.08 %이고 46666은 0.10 %이므로 혼동이 발생한 곳이라고 생각합니다)

따라서 where 절의 결과가 35000에서 46666 행 사이 인 경우 (이것이 최상위 절 아래에 있습니다!) 클러스터되지 않은 클러스터가 사용되지 않고 클러스터 된 인덱스 스캔이 사용될 가능성이 큽니다.

이를 변경하는 유일한 두 가지 방법은 다음과 같습니다.

  1. where 절을 더 선택적으로 만드십시오. (가능하다면)
  2. *를 삭제하고 몇 개의 열만 선택하면 포함 인덱스를 사용할 수 있습니다.

이제 select *를 사용할 때도 포함 인덱스를 만들 수 있습니다. 그러나 삽입 / 업데이트 / 삭제에 엄청난 오버 헤드가 발생합니다. 최선의 솔루션인지 확인하려면 작업 부하 (읽기 / 쓰기)에 대해 더 많이 알아야합니다.

datetime에서 smalldatetime으로 변경하면 클러스터형 인덱스의 크기가 16 % 감소하고 비 클러스터형 인덱스의 크기가 24 % 감소합니다.


스캔 임계 값은 일반적으로 해당 임계 값보다 훨씬 낮지 만 (10 % 또는 훨씬 낮음), 범위는 1 년 전부터 하루가되기 때문에 해당 임계 값을 설정해서는 안됩니다. 커버링 인덱스가 추가 되었기 때문에 클러스터형 인덱스 스캔은 제공되지 않습니다. 이 지수는 WHERE 절이 SARG를 가능하게하기 때문에 선호되어야한다.
RBarryYoung

@RBarryYoung [EnteredDate], [DeviceID]의 클러스터되지 않은 인덱스가 처음에 사용되지 않은 이유를 설명하려고했습니다. 임계 값에 관해서는, 나는 우리 둘 다 동의한다고 생각한다. 나는 단지 페이지 관점에서만 이야기하고있다. 더 명확하게 답변을 변경하겠습니다.
Edward Dortland

내가 대답하고있는 것을 더 명확하게하기 위해 답을 변경했습니다. @RBarryYoung이 제안한 커버링 인덱스가 사용되지 않는 이유를 설명 할 수 없습니다. 방금 여기 백만 행에서 테스트했으며 덮는 색인을 사용하여 최적화했습니다.
Edward Dortland

매우 포괄적 인 답변에 감사드립니다. 워크로드와 관련하여, 테이블에는 5 분 간격으로 150-300 개의 삽입물과보고 목적으로 하루에 몇 번의 판독 값이 있습니다.
Nate

커버링 인덱스의 오버 헤드 헤드는 테이블이 좁고 "커버링"은 이미 대부분의 행을 포함하는 기존 인덱스에 추가 된 것이므로 중요하지 않습니다.
RBarryYoung

8

PK가 클러스터 된 특별한 이유가 있습니까? 많은 사람들이 기본적으로 그렇게하기 때문에 PK를 클러스터해야한다고 생각합니다. 아뇨. 클러스터형 인덱스는 일반적으로 이와 같은 범위 쿼리 나 자식 테이블의 외래 키에 가장 적합합니다.

클러스터링 인덱스의 효과는 데이터가 클러스터 b 트리의 리프 노드에 저장되기 때문에 모든 데이터를 묶습니다. 따라서 범위의 '너무 넓은'을 요구하지 않는다고 가정하면 옵티마이 저는 b 트리의 어떤 부분에 데이터가 포함되어 있는지 정확히 알고 행 식별자를 찾은 다음 데이터가있는 곳으로 건너 뛸 필요가 없습니다. (NC 색인을 처리 할 때와 같이)입니다. 너무 넓은 범위는 무엇입니까? 우스운 예는 1 년 분의 레코드 만있는 테이블에서 11 개월의 데이터를 요청하는 것입니다. 통계가 최신 상태라고 가정하면 하루 동안의 데이터를 가져 오는 것은 문제가되지 않습니다. (어제 데이터를 찾고 있고 3 일 동안 통계를 업데이트하지 않은 경우 옵티마이 저가 문제를 일으킬 수 있습니다.)

"SELECT *"쿼리를 실행하고 있기 때문에 엔진은 테이블에있는 모든 열을 반환해야합니다 (누군가가 앱에서 필요로하지 않는 새로운 열을 추가하더라도). 포함 된 열이 있으면별로 도움이되지 않습니다. (인덱스에 테이블의 모든 열을 포함하는 경우 잘못된 작업이 수행됩니다.) 옵티마이 저는 아마도 해당 NC 인덱스를 무시합니다.

그래서 뭐 할까?

NC 인덱스를 삭제하고 클러스터 된 PK를 클러스터되지 않은 것으로 변경하고 [DateEntered]에 클러스터 된 인덱스를 생성하는 것이 좋습니다. 그렇지 않으면 입증 될 때까지 단순할수록 좋습니다.


행이 증가하는 순서로 삽입되었다고 가정하면 이것이 가장 간단한 대답이지만 비선형 순서로 삽입하면 조각화가 발생합니다.
Kirk Broadhurst

b- 트리 구조에 데이터를 추가하면 균형을 잃게됩니다. 클러스터 순서로 행을 추가하더라도 인덱스의 균형이 유지되지 않습니다. 테이블을 다시 인덱싱하면 조각화가 제거되고 DBA는 "충분한"데이터가 테이블에 추가 된 후 테이블을 다시 인덱싱해야한다고 알려줍니다. ( "충분한"의 정의는 토론 될 수도 있고, "언제"는 토론 일 수도 있습니다.) 질문에 어떤 이유로 든 색인 생성을 다시 수행 할 수 없다는 내용은 없습니다.
darin 해협

4

"*"가있는 한, 큰 차이를 만들 것이라고 생각할 수있는 유일한 것은 인덱스 정의를 다음과 같이 변경하는 것입니다.

CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats] 
(
    [DateEntered] ASC,
    [DeviceID] ASC
)INCLUDE (ID, IsWebUp, IsPingUp, IsPUp)
 WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

주석에서 언급했듯이 해당 인덱스를 사용해야하지만 그렇지 않은 경우 ORDER BY 또는 인덱스 힌트로 설득 할 수 없습니다.


방금 이것을 시도했지만 여전히 거의 같은 자리에 있습니다. 2500ms는 서버 응답을 기다리고 10ms 클라이언트 프로세스 시간을 기다립니다.
Nate

쿼리 계획을 게시하십시오.
RBarryYoung

클러스터형 인덱스를 사용하고있는 것 같습니다. (선택 비용 : 0 % <-최고 비용 : 20 % <-클러스터형 인덱스 스캔 PK_Heartbeats 비용 : 80 %)
Nate

네, 맞지 않습니다. 통계 / 최적화기를 버리는 것입니다. 새 인덱스를 사용하도록 힌트를 추가하십시오.
RBarryYoung

@Max Vernon : 어쩌면 쿼리 계획에 플래그가 지정되어 있어야합니다.
RBarryYoung

3

나는 이것을 조금 다르게 볼 것이다.

  • 예, 나는 그것이 오래된 실이라는 것을 알고 있지만 흥미가 있습니다.

날짜 시간 열을 덤프했습니다-int로 변경하십시오. 룩업 테이블이 있거나 날짜를 변환하십시오.

클러스터형 인덱스 덤프-힙으로 남겨두고 날짜를 나타내는 새 INT 열에 비 클러스터형 인덱스를 만듭니다. 즉, 오늘은 20121015입니다. 순서는 중요합니다. 테이블을 얼마나 자주로드하는지에 따라 DESC 순서로 해당 인덱스를 작성하십시오. 유지 보수 비용이 더 높으므로 채우기 비율 또는 파티셔닝을 도입하려고합니다. 파티셔닝은 또한 실행 시간을 줄이는 데 도움이됩니다.

마지막으로 SQL 2012를 사용할 수 있으면 SEQUENCE를 사용해보십시오. 삽입에 대해 identity ()보다 성능이 우수합니다.


재미있는 해결책. 내 질문에서 분명하지는 않지만 DateTime의 시간 부분은 매우 중요합니다. 일반적으로 해당 기간 동안 특정 시간을 검토하기 위해 날짜를 기준으로 쿼리합니다. 이를 고려하여이 솔루션을 어떻게 조정 하시겠습니까?
Nate

이 경우 날짜 시간 열을 유지하고 날짜의 int 열을 추가하십시오 (범위는 시간 요소가 아닌 날짜 요소를 기반으로하기 때문에). TIME 데이터 유형 사용을 고려한 다음 시간을 날짜와 효과적으로 분리 할 수도 있습니다. 이러한 방식으로 데이터 풋 프린트가 더 작아지고 여전히 컬럼의 시간 요소가 있습니다.
Jeremy Lowell

1
왜 내가 이것을 일찍 놓쳤는 지 모르겠지만 클러스터형 인덱스와 비 클러스터형 인덱스에서도 행 압축을 사용합니다. 방금 테이블을 사용하여 빠른 테스트를 수행했으며 여기에 내가 찾은 내용이 있습니다. 위에서 정의한 테이블에 데이터 집합 (5,600 만 행)을 만들었습니다. 클러스터 및 비 클러스터형 인덱스를 압축 (행)했습니다. 정확한 쿼리를 기반으로 한 논리적 읽기가 2,074에서 1,433으로 감소했습니다. 그것은 상당히 감소한 것이며 혼자서도 당신을 도울 것이라고 확신합니다-그리고 그것은 매우 낮은 위험입니다.
Jeremy Lowell
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.