문제

이 쿼리가 실행중인 상자는 데이터 센터에서 실행되는 전용 서버입니다.

AMD Opteron 1354 Quad-Core 2.20GHz 2GB의 RAM Windows Server 2008 X64 (예, 2GB의 RAM 만 있다는 것을 알고 있습니다. 프로젝트가 생방송 될 때 8GB로 업그레이드하고 있습니다).

그래서 나는 테이블에 250,000 개의 더미 행을 만들어서 LINQ에서 SQL에서 SQL을 생성하는 일부 쿼리를 스트레스 테스트하고 끔찍한 시간을 보냈으며 그 중 하나가 터무니없는 시간이 걸렸다는 것을 알았습니다.

이 쿼리가 인덱스로 17 초로 줄었지만이 답변을 위해 처음부터 끝까지 이동하기 위해 제거했습니다. 인덱스 만 기본 키입니다.

Stories table --
[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NOT NULL,
[CategoryID] [int] NOT NULL,
[VoteCount] [int] NOT NULL,
[CommentCount] [int] NOT NULL,
[Title] [nvarchar](96) NOT NULL,
[Description] [nvarchar](1024) NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[UniqueName] [nvarchar](96) NOT NULL,
[Url] [nvarchar](512) NOT NULL,
[LastActivityAt] [datetime] NOT NULL,

Categories table --
[ID] [int] IDENTITY(1,1) NOT NULL,
[ShortName] [nvarchar](8) NOT NULL,
[Name] [nvarchar](64) NOT NULL,

Users table --
[ID] [int] IDENTITY(1,1) NOT NULL,
[Username] [nvarchar](32) NOT NULL,
[Password] [nvarchar](64) NOT NULL,
[Email] [nvarchar](320) NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[LastActivityAt] [datetime] NOT NULL,

현재 데이터베이스에는 1 명의 사용자, 1 개의 카테고리 및 250,000 개의 스토리가 있으며이 쿼리를 실행하려고했습니다.

SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
INNER JOIN Users ON Users.ID = Stories.UserID
ORDER BY Stories.LastActivityAt

쿼리는 실행하는 데 52 초가 걸리고 CPU 사용량 호버는 2-3%, Membery는 1.1GB, 900MB는 무료이지만 디스크 사용은 제어 불가능합니다. 그것은 @ 100MB/sec이고 그 중 2/3은 tempdb.mdf에 쓰고 나머지는 tempdb.mdf에서 읽습니다.

이제 흥미로운 부분을 위해 ...

SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
INNER JOIN Users ON Users.ID = Stories.UserID

SELECT TOP(10) *
FROM Stories
INNER JOIN Users ON Users.ID = Stories.UserID
ORDER BY Stories.LastActivityAt

SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
ORDER BY Stories.LastActivityAt

이 쿼리 중 3 개는 거의 즉각적입니다.

첫 번째 쿼리에 대한 실행 계획.
http://i43.tinypic.com/xp6gi1.png

다른 3 쿼리에 대한 실행 계획 (순서대로).
http://i43.tinypic.com/30124bp.png
http://i44.tinypic.com/13yjml1.png
http://i43.tinypic.com/33ue7fb.png

어떤 도움이든 큰 감사를드립니다.

인덱스를 추가 한 후 EXEC 계획 (다시 17 초까지).
http://i39.tinypic.com/2008ytx.png

나는 모두에게 많은 도움이되는 피드백을 받았으며 감사합니다. 나는 이것에 새로운 각도를 시도했습니다. 필요한 이야기를 쿼리 한 다음 별도의 쿼리에서 카테고리와 사용자를 얻고 3 개의 쿼리로 250ms 만 필요합니다 ... 문제를 이해하지 못하지만 작동하는 경우 250ms에서 시간이 걸리지 않습니다. 그것을 고수하십시오. 다음은 이것을 테스트하는 데 사용한 코드입니다.

DBDataContext db = new DBDataContext();
Console.ReadLine();

Stopwatch sw = Stopwatch.StartNew();

var stories = db.Stories.OrderBy(s => s.LastActivityAt).Take(10).ToList();
var storyIDs = stories.Select(c => c.ID);
var categories = db.Categories.Where(c => storyIDs.Contains(c.ID)).ToList();
var users = db.Users.Where(u => storyIDs.Contains(u.ID)).ToList();

sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
도움이 되었습니까?

해결책

Stories.lastactivityat에 인덱스를 추가 해보십시오. 실행 계획의 클러스터 인덱스 스캔은 정렬 때문일 수 있다고 생각합니다.

편집 : 쿼리가 몇 바이트 길이로 줄이 순간적으로 반환되었지만 이미 5 분 동안 실행 중이며 2K Varchar를 추가 한 후에도 여전히 진행 중이며 Mitch는 포인트가 있다고 생각합니다. 그것은 아무것도 섞인 데이터의 볼륨이지만, 이것은 쿼리에서 수정 될 수 있습니다.

조인, 정렬 및 상단 (10)을보기 또는 중첩 쿼리에 넣은 다음 스토리 테이블에 다시 연결하여 필요한 10 행에 대한 나머지 데이터를 얻으십시오.

이와 같이:

select * from 
(
    SELECT TOP(10) id, categoryID, userID
    FROM Stories
    ORDER BY Stories.LastActivityAt
) s
INNER JOIN Stories ON Stories.ID = s.id
INNER JOIN Categories ON Categories.ID = s.CategoryID
INNER JOIN Users ON Users.ID = s.UserID

LastActivityAt에 대한 색인이있는 경우 매우 빠르게 실행됩니다.

다른 팁

따라서 첫 번째 부분을 올바르게 읽으면 17 초 안에 인덱스로 응답합니다. 여전히 10 개의 레코드를 chug하는 데 여전히 시간입니다. 나는 시간이 절로 순서대로 있다고 생각합니다. 나는 lastActivityat, userId, categoryID에 대한 색인을 원할 것입니다. 재미를 위해 주문을 제거하고 10 개의 레코드를 신속하게 반환하는지 확인하십시오. 그렇다면, 당신은 그것이 다른 테이블과의 결합에 있지 않다는 것을 알고 있습니다. 또한 Neil이 언급했듯이 3 개의 테이블 열이 TempDB에 있으므로 *를 필요한 열로 교체하는 것이 도움이됩니다.

실행 계획을 살펴보면 여분의 종류를 알 수 있습니다. 시간이 걸리는 순서라고 생각합니다. 나는 당신이 3의 색인을 가지고 있다고 가정하고 17 초라고 가정합니다. 또한 인덱스 튜닝 마법사를 통해 쿼리를 실행하는 것이 좋습니다.

첫 번째 제안은 *를 제거하고 필요한 최소 열로 바꾸는 것입니다.

둘째, 방아쇠가 관련되어 있습니까? lastactivityat 필드를 업데이트 할 것이 있습니까?

문제 쿼리를 기반으로 테이블에 조합 인덱스를 추가하십시오. Stories (CategoryId, userId, lastActivityat)

하드웨어 설정에서 디스크를 최대화하고 있습니다.

귀하의 데이터/로그/tempdb 파일 배치에 대한 귀하의 의견이 주어지면, 튜닝의 양은 반드시가 될 것이라고 생각합니다.

250,000 행이 작습니다. 1 천만 행으로 문제가 얼마나 나쁜지 상상해보십시오.

나는 당신이 TempDB를 자신의 물리적 드라이브로 옮기는 것이 좋습니다 (선호하는 습격 0).

좋아, 그래서 내 테스트 기계가 빠르지 않습니다. 사실 정말 느립니다. 1.6GHz, N 1GB RAM, 다중 디스크 없음, SQL Server, OS 및 엑스트라 용 단일 (느린 읽기) 디스크 만 있습니다.

기본 및 외국 키가 정의 된 테이블을 만들었습니다. 2 개의 카테고리, 500 명의 임의의 사용자 및 250000 랜덤 스토리를 삽입했습니다.

위의 첫 번째 쿼리를 실행하는 데 16 초가 걸립니다 (계획 캐시도 없음). LastActivityAt 열을 색인하면 1 초 미만의 결과가 나타납니다 (여기서도 계획 캐시도 없음).

여기 내가이 모든 일을했던 대본이 있습니다.

    --Categories table --
Create table Categories (
[ID] [int] IDENTITY(1,1) primary key NOT NULL,
[ShortName] [nvarchar](8) NOT NULL,
[Name] [nvarchar](64) NOT NULL)

--Users table --
Create table Users(
[ID] [int] IDENTITY(1,1) primary key NOT NULL,
[Username] [nvarchar](32) NOT NULL,
[Password] [nvarchar](64) NOT NULL,
[Email] [nvarchar](320) NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[LastActivityAt] [datetime] NOT NULL
)
go

-- Stories table --
Create table Stories(
[ID] [int] IDENTITY(1,1) primary key NOT NULL,
[UserID] [int] NOT NULL references Users ,
[CategoryID] [int] NOT NULL references Categories,
[VoteCount] [int] NOT NULL,
[CommentCount] [int] NOT NULL,
[Title] [nvarchar](96) NOT NULL,
[Description] [nvarchar](1024) NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[UniqueName] [nvarchar](96) NOT NULL,
[Url] [nvarchar](512) NOT NULL,
[LastActivityAt] [datetime] NOT NULL)

Insert into Categories (ShortName, Name) 
Values ('cat1', 'Test Category One')

Insert into Categories (ShortName, Name) 
Values ('cat2', 'Test Category Two')

--Dummy Users
Insert into Users
Select top 500
UserName=left(SO.name+SC.name, 32)
, Password=left(reverse(SC.name+SO.name), 64)
, Email=Left(SO.name, 128)+'@'+left(SC.name, 123)+'.com'
, CreatedAt='1899-12-31'
, LastActivityAt=GETDATE()
from sysobjects SO 
Inner Join syscolumns SC on SO.id=SC.id
go

--dummy stories!
-- A Count is given every 10000 record inserts (could be faster)
-- RBAR method!
set nocount on
Declare @count as bigint
Set @count = 0
begin transaction
while @count<=250000
begin
Insert into Stories
Select
  USERID=floor(((500 + 1) - 1) * RAND() + 1)
, CategoryID=floor(((2 + 1) - 1) * RAND() + 1)
, votecount=floor(((10 + 1) - 1) * RAND() + 1)
, commentcount=floor(((8 + 1) - 1) * RAND() + 1)
, Title=Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36))
, Description=Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36))
, CreatedAt='1899-12-31'
, UniqueName=Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36)) 
, Url=Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36))
, LastActivityAt=Dateadd(day, -floor(((600 + 1) - 1) * RAND() + 1), GETDATE())
If @count % 10000=0
Begin
Print @count
Commit
begin transaction
End
Set @count=@count+1
end 
set nocount off
go

--returns in 16 seconds
DBCC DROPCLEANBUFFERS
SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
INNER JOIN Users ON Users.ID = Stories.UserID
ORDER BY Stories.LastActivityAt
go

--Now create an index
Create index IX_LastADate on Stories (LastActivityAt asc)
go
--With an index returns in less than a second
DBCC DROPCLEANBUFFERS
SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
INNER JOIN Users ON Users.ID = Stories.UserID
ORDER BY Stories.LastActivityAt
go

종류는 분명히 느린 속도가 발생하는 곳입니다. 정렬은 주로 TEMPDB에서 완료되며 큰 테이블로 인해 많은 것이 추가됩니다. 이 열에 인덱스가 있으면 주문시 성능이 향상됩니다.

또한 기본 및 외국 키를 정의하면 SQL Server가 엄청나게 도움이됩니다.

코드에 나열된 방법은 우아하며 기본적으로 CDONNER가 SQL이 아닌 C#을 제외하고 쓴 동일한 응답입니다. DB를 조정하면 아마도 더 나은 결과를 얻을 수 있습니다!

-Kris

각 쿼리를 실행하기 전에 SQL Server 캐시를 지 웠습니까?

SQL 2000에서는 DBCC DropcleanBuffers와 같은 것입니다. 자세한 정보는 Google 명령입니다.

쿼리를 보면

category.id stories.categoryId users.id stories.userid

그리고 아마도 이야기. lastactivityat

그러나 예, 결과는 가짜의 캐싱이 될 수있는 것 같습니다.

한동안 SQL Server와 함께 일할 때 쿼리의 가장 작은 변경조차도 응답 시간이 크게 다른 것으로 나타났습니다. 초기 질문에서 읽은 내용과 쿼리 계획을 살펴보면 최적화가 가장 좋은 방법은 부분 결과를 형성 한 다음 별도의 단계로 분류하는 것이라고 결정했다고 생각합니다. 부분 결과는 사용자와 스토리 테이블의 합성물입니다. 이것은 tempdb로 형성됩니다. 따라서 과도한 디스크 액세스는이 임시 테이블의 형성 및 정렬로 인한 것입니다.

솔루션은 Stories.lastactivityat, Stories.userid, Stories.categoryid에 대한 복합 색인을 만드는 것이어야합니다. 순서는 매우 중요합니다. Field LastActivityAt가 먼저 있어야합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top