Pergunta

A caixa esta consulta está sendo executado em um servidor dedicado em execução em um datacenter.

AMD Opteron 1354 Quad-Core 2.20GHz 2 GB de RAM 2008 x64 Windows Server (Sim, eu sei que eu só tem 2 GB de RAM, estou atualizando para 8GB quando o projeto vai viver).

Assim que eu passei e criou 250.000 linhas fictícios em uma tabela para realmente teste de estresse algumas consultas que LINQ to SQL gera e se certificar de que eles não são a terrível e eu notei um deles estava tomando uma quantidade absurda de tempo.

Eu tive esse baixo consulta para 17 segundos com os índices, mas eu removi-los por causa desta resposta para ir do início ao fim. Somente índices são chaves primárias.

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,

Actualmente no banco de dados Há 1 utilizador, 1 categoria e 250.000 histórias e eu tentei para executar esta consulta.

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

consulta leva 52 segundos para correr, CPU paira uso em 2-3%, Membery é 1.1GB, 900MB livre, mas o uso do disco parece fora de controle. É @ 100 MB / seg com 2/3 do que sendo gravações tempdb.mdf eo resto é a leitura de tempdb.mdf.

Agora, a parte interessante ...

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

Todos os 3 dessas consultas são praticamente instantânea.

plano de Exec para a primeira consulta.
http://i43.tinypic.com/xp6gi1.png

planos Exec para outros 3 consultas (em ordem).
http://i43.tinypic.com/30124bp.png
http://i44.tinypic.com/13yjml1.png
http://i43.tinypic.com/33ue7fb.png

Qualquer ajuda seria muito apreciada.

plano Exec depois de adicionar índices (até 17 segundos novamente).
http://i39.tinypic.com/2008ytx.png

Eu recebi um monte de comentários úteis de todos e agradeço-lhe, eu tentei um novo ângulo para isso. Eu consultar as histórias que eu preciso, então, em consultas separadas obter as Categorias e Usuários e com 3 consultas levou apenas 250ms ... Eu não entendo o problema, mas se ele funciona e, 250ms não menos por enquanto eu vou ficar com isso. Aqui está o código que usei para testar isto.

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);
Foi útil?

Solução

Tente adicionar um índice em Stories.LastActivityAt. Eu acho que a verificação de índice em cluster no plano de execução pode ser devido à classificação.

Edit: Desde a minha consulta retornou em um instante com linhas apenas alguns bytes de comprimento, mas tem funcionado por 5 minutos e já está indo ainda depois que eu adicionei um varchar 2K, acho que Mitch tem um ponto. É o volume de que os dados que está embaralhado para nada, mas isso pode ser corrigido na consulta.

Tente colocar a junção, classificar e superior (10) em uma visão ou em uma consulta aninhada, e depois juntar de volta contra a mesa de história para obter o resto dos dados apenas para as 10 linhas que você precisa.

Como esta:

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

Se você tiver um índice em LastActivityAt, este deve correr muito rápido.

Outras dicas

Então, se eu ler a primeira parte corretamente, ele responde em 17 segundos, com um índice. Que ainda é um pouco para chug fora 10 registros. Estou pensando o tempo é da ordem pela cláusula. Eu iria querer um índice em LastActivityAt, UserID, CategoryID. Apenas por diversão, remova a ordem por e ver se ele retorna os 10 registros rapidamente. Se isso acontecer, então você sabe que não está na junta-se às outras tabelas. Também seria útil para substituir o * com as colunas necessárias como todos os 3 colunas da tabela estão na tempdb como você está classificando -. Como Neil mencionado

Olhando para os planos de execução que você vai notar o tipo Extra - Eu acredito que é a ordem por que vai demorar algum tempo. Estou assumindo que você tinha um índice com a 3 e foi 17 segundos ... então você pode querer um índice para a junção critérios (UserID categoryID) e outro para lastactivityat - ver se que executa melhor. Também seria bom para executar a consulta através do assistente índice de tuning.

A minha primeira sugestão é para remover a *, e substituí-lo com as colunas mínimas que você precisa.

Em segundo lugar, existe um gatilho envolvido? Algo que iria atualizar o campo LastActivityAt?

Com base na sua consulta problema, tente adicionar um índice de combinação na Stories mesa (CategoryID, UserID, LastActivityAt)

Você está estourar o limite dos discos em sua configuração de hardware.

Dada seus comentários sobre a sua colocação Arquivo de Dados / Log / tempdb, eu acho que qualquer quantidade de ajuste vai ser um bandaid.

250.000 linhas é pequeno. Imagine o quão ruim seus problemas vão ser com 10 milhões de linhas.

Eu sugiro que você se mover tempdb para sua própria unidade física (a RAID preferível 0).

Ok, então minha máquina de teste não é rápido. Na verdade, é muito lento. É 1.6 ghz, n 1 GB de RAM, no vários discos, apenas um único (leia lento) de disco para SQL Server, sistema operativo e extras.

Eu criei suas tabelas com chaves primárias e estrangeiras definidas. Inserida 2 categorias, 500 usuários aleatórios, e 250000 histórias aleatórias.

Running a primeira consulta acima leva 16 segundos (sem cache do plano, quer). Se eu indexar a coluna LastActivityAt eu obter resultados em menos de um segundo (sem cache do plano aqui também).

Aqui está o script que eu usei para fazer tudo isso.

    --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

O tipo é definitivamente onde seu baixo lento está ocorrendo. Classificando principalmente é feito no tempdb e uma grande mesa vai causar muitos a ser adicionado. Ter um índice sobre esta coluna vai certamente melhorar o desempenho em uma ordem de.

Além disso, definindo suas chaves primárias e estrangeiras ajuda SQL Server immensly

Seu método que está listado em seu código é elegante, e basicamente a mesma resposta que cdonner escreveu exceto em c # e não sql. Ajustando o db provavelmente dará resultados ainda melhores!

- Kris

Você já limpou o cache SQL Server antes de executar cada um a consulta?

No SQL 2000, é algo como DBCC DROPCLEANBUFFERS. Google o comando para obter mais informações.

Olhando para a consulta, eu teria um índice para

Categories.ID Stories.CategoryID Users.ID Stories.UserID

e possivelmente Stories.LastActivityAt

Mas sim, parece que o resultado poderia ser cos de caching' falsos.

Quando você trabalhou com SQL Server por algum tempo, você vai descobrir que mesmo as menores mudanças para uma consulta pode causar muito diferentes tempos de resposta. Pelo que eu li na questão inicial, e olhando para o plano de consulta, eu suspeito que o otimizador decidiu que a melhor abordagem é para formar um resultado parcial e em seguida, classificar isso como uma etapa separada. O resultado parcial é um compósito dos utilizadores e histórias tabelas. Este é formado em tempdb. Assim, o acesso ao disco excessiva é devido à formação e ordenação desta tabela temporária.

Concordo que a solução deve ser o de criar um índice composto em Stories.LastActivityAt, Stories.UserId, Stories.CategoryId. A ordem é muito importante, o LastActivityAt campo deve ser o primeiro.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top