varchar (max)로 인해 tempdb에 정렬 유출


10

32GB의 서버에서 최대 메모리가 25GB 인 SQL Server 2014 SP2를 실행중인 경우 두 개의 테이블이 있습니다. 여기서 두 테이블의 단순화 된 구조를 찾을 수 있습니다.

CREATE TABLE [dbo].[Settings](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceId] [int] NULL,
    [typeID] [int] NULL,
    [remark] [varchar](max) NULL,
    CONSTRAINT [PK_Settings] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Resources](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [resourceUID] [int] NULL,
 CONSTRAINT [PK_Resources] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
GO

다음과 같은 비 클러스터형 인덱스

CREATE NONCLUSTERED INDEX [IX_UID] ON [dbo].[Resources]
(
    [resourceUID] ASC
)

CREATE NONCLUSTERED INDEX [IX_Test] ON [dbo].[Settings]
(
    [resourceId] ASC,
    [typeID] ASC
)

데이터베이스는 compatibility level120 으로 구성되어 있습니다.

쿼리를 실행하면 에 유출이 발생합니다 tempdb. 이것이 내가 쿼리를 실행하는 방법입니다.

exec sp_executesql N'
select r.id,remark
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38

[remark]필드를 선택하지 않으면 유출이 발생 하지 않습니다 . 내 첫 번째 반응은 중첩 루프 연산자의 예상 행 수가 적어 유출이 발생했다는 것입니다.

그래서 설정 테이블에 5 개의 날짜 시간과 5 개의 정수 열을 추가하고 내 select 문에 추가합니다. 쿼리를 실행할 때 유출이 발생하지 않습니다.

유출 [remark]이 선택된 경우에만 발생하는 이유는 무엇 입니까? 아마도 이것이 사실이라는 것과 관련이있을 것 varchar(max)입니다. 엎 지르지 않도록 어떻게해야 tempdb합니까?

OPTION (RECOMPILE)쿼리에 추가해도 차이가 없습니다.


시도해 볼 수 있습니다 select r.id, LEFT(remark, 512)(또는 현명한 부분 문자열 길이).
mustaccio

@ 포레스트 : 문제를 시뮬레이션하는 데 필요한 데이터를 재현하려고합니다. 처음에는 중첩 루프의 낮은 추정치와 관련이 있습니다. 내 더미 데이터에서 예상 행 수는 훨씬 높고 흘림이 발생하지 않습니다.
Frederik Vanderhaegen

답변:


10

여기에는 몇 가지 가능한 해결 방법이 있습니다.

아마 그 길을 가지 않더라도 수동으로 메모리 부여를 조정할 수 있습니다 .

최대 길이 열을 잡기 전에 CTE와 TOP을 사용하여 정렬을 낮출 수도 있습니다. 아래와 같이 보일 것입니다.

WITH CTE AS (
SELECT TOP 1000000000 r.ID, s.ID AS ID2, s.typeID
FROM Resources r
inner join Settings s on resourceid=r.id
where resourceUID=@UID
ORDER BY s.typeID
)
SELECT c.ID, ca.remark
FROM CTE c
CROSS APPLY (SELECT remark FROM dbo.Settings s WHERE s.id = c.ID2) ca(remark)
ORDER BY c.typeID

개념 증명 dbfiddle here . 샘플 데이터는 여전히 감사하겠습니다!

Paul White의 훌륭한 분석을 읽으려면 여기를 읽으십시오.


7

유출이 [비고]를 선택한 경우에만 발생하는 이유는 무엇입니까?

정렬되는 큰 문자열 데이터에 대해 충분한 메모리 부여를 얻지 못하기 때문에 해당 열을 포함하면 유출이 발생합니다.

실제 행 수가 예상 행 수보다 10 배 더 많기 때문에 (1,302 실제 대 126 추정) 메모리 용량이 충분하지 않습니다.

견적이 왜 꺼져 있습니까? SQL Server가 dbo.Sets resourceid에 38 행이 하나만 있다고 생각하는 이유는 무엇 입니까?

통계 문제 일 수 있으며 실행 DBCC SHOW_STATISTICS('dbo.Settings', 'IX_Test')하여 해당 히스토그램 단계의 수를 확인할 수 있습니다 . 그러나 실행 계획은 통계가 가능한 한 완전하고 최신 상태임을 나타냅니다.

통계가 도움이되지 않기 때문에 가장 좋은 방법은 Forrest그의 답변 에서 다룬 쿼리 재 작성 일 것입니다 .


3

나에게 그것은 where쿼리 의 절이 문제를주는 것으로 보이며 OPTION(RECOMPILE)사용 되더라도 낮은 추정치의 원인입니다 .

몇 가지 테스트 데이터를 만들었고 결국 두 가지 솔루션이 나타났습니다.이 ID필드를 resources변수 (항상 고유 한 경우) 또는 임시 테이블에 저장하면 둘 이상의 값을 가질 수 있습니다 ID.

기본 테스트 기록

SET NOCOUNT ON
DECLARE @i int= 1;
WHILE @i <= 10000
BEGIN
INSERT INTO [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(@i,@i,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here'); -- 23254 character length on each value
INSERT INTO  [dbo].[Resources](resourceUID)
VALUES(@i);
SET @i += 1;
END

OP (1300 레코드)와 동일한 대략의 결과 집합을 얻으려면 '탐색'값을 삽입하십시오.

INSERT INTO  [dbo].[Settings]([resourceId],[typeID],remark)
VALUES(38,38,'KEPT THESE VALUES OUT BECAUSE IT WOULD CLUTTER THE EXAMPLES, VALUES OVER 8000 Chars entered here')
GO 1300

OP와 일치하도록 compat 및 업데이트 통계 변경

ALTER DATABASE StackOverflow SET COMPATIBILITY_LEVEL = 120;
UPDATE STATISTICS settings WITH FULLSCAN;
UPDATE STATISTICS resources WITH FULLSCAN;

원래 검색어

exec sp_executesql N'
select r.id
FROM Resources r
inner join Settings on resourceid=r.id
where resourceUID=@UID
ORDER BY typeID',
N'@UID int',
@UID=38

1300 행이 반환되는 동안 내 예상치가 1 열로 인해 더 나빠 졌습니다. OP가 언급했듯이 추가해도 중요하지 않습니다.OPTION(RECOMPILE)

주목할 점은 where 절을 제거 할 때 추정치가 100 % 정확하다는 것입니다. 두 테이블의 모든 데이터를 사용하므로 예상됩니다.

요점을 증명하기 위해 이전 쿼리에서와 동일한 인덱스를 사용하도록 인덱스를 강요했습니다.

exec sp_executesql N'
select r.id,remark
FROM Resources r with(index([IX_UID]))
inner join Settings WITH(INDEX([IX_Test])) 
on resourceid=r.id
ORDER BY typeID',
N'@UID int',
@UID=38

예상대로 좋은 추정치.

그렇다면 더 나은 견적을 얻기 위해 무엇을 바꿀 수 있지만 여전히 가치를 추구 할 수 있습니까?

예를 들어 OP가 제공 한 예제에서와 같이 @UID가 고유 한 경우 변수에서 id반환 된 단일 resources변수를 넣은 다음 OPTION (RECOMPILE)을 사용하여 해당 변수를 찾을 수 있습니다.

DECLARE @UID int =38 , @RID int;
SELECT @RID=r.id from 
Resources r where resourceUID = @UID;

SELECT @uid, remark 
from Settings 
where resourceId = @uid 
Order by typeID
OPTION(RECOMPILE);

100 % 정확한 추정치

그러나 리소스에 여러 resourceUID가있는 경우 어떻게해야합니까?

테스트 데이터를 추가하십시오

INSERT INTO Resources(ResourceUID)
VALUES (38);
go 50

이것은 임시 테이블로 해결할 수 있습니다

CREATE TABLE #RID (id int)
DECLARE @UID int =38 
INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID

정확한 추정치로 다시 .

이것은 내 자신의 데이터 세트 YMMV로 수행되었습니다.


sp_executesql로 작성

변수로

exec sp_executesql N'
DECLARE  @RID int;
    SELECT @RID=r.id from 
    Resources r where resourceUID = @UID;

    SELECT @uid, remark 
    from Settings 
    where resourceId = @uid 
    Order by typeID
    OPTION(RECOMPILE);',
N'@UID int',
@UID=38

임시 테이블

exec sp_executesql N'

CREATE TABLE #RID (id int)

INSERT INTO #RID
SELECT r.id 
from 
Resources r where resourceUID = @UID

SELECT @uid, remark 
from Settings  s
INNER JOIN #RID r
ON r.id =s.resourceId
Order by typeID
OPTION(RECOMPILE)

DROP TABLE #RID',
N'@UID int',
@UID=38

테스트에서 여전히 100 % 정확한 추정치

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.