Como otimizar a consulta

9

Eu tenho uma estrutura de banco de dados semelhante a esta,

CREATE TABLE [dbo].[Dispatch](
    [DispatchId] [int] NOT NULL,
    [ContractId] [int] NOT NULL,
    [DispatchDescription] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Dispatch] PRIMARY KEY CLUSTERED 
(
    [DispatchId] ASC,
    [ContractId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[DispatchLink](
    [ContractLink1] [int] NOT NULL,
    [DispatchLink1] [int] NOT NULL,
    [ContractLink2] [int] NOT NULL,
    [DispatchLink2] [int] NOT NULL
) ON [PRIMARY]

GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (1, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (2, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (3, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (4, 1, N'Test')
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 2)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 3)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 3, 1, 2)
GO

O objetivo da tabela DispatchLink é vincular dois registros de expedição. A propósito, estou usando uma chave primária composta na minha tabela de expedição por causa do legado, por isso não posso mudar isso sem muita dor. Além disso, a tabela de links pode não ser a maneira correta de fazer isso? Mas novamente legado.

Então, minha pergunta, se eu executar esta consulta

select * from Dispatch d
inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 and d.ContractId = dl.ContractLink1
or d.DispatchId = dl.DispatchLink2 and d.ContractId = dl.ContractLink2

Nunca consigo fazer uma busca de índice na tabela DispatchLink. Ele sempre faz uma verificação completa do índice. Isso é bom com alguns registros, mas quando você tem 50000 nessa tabela, ele varre 50000 registros no índice de acordo com o plano de consulta. É porque existem 'ands' e 'ors' na cláusula join, mas não consigo entender por que o SQL não pode fazer algumas buscas de índice, uma para o lado esquerdo da 'ou', e um para o lado direito do 'ou'.

Eu gostaria de uma explicação para isso, não uma sugestão para tornar a consulta mais rápida, a menos que isso possa ser feito sem ajustar a consulta. O motivo é que estou usando a consulta acima como um filtro de junção de replicação de mesclagem, portanto, infelizmente, não posso adicionar outro tipo de consulta.

UPDATE: por exemplo, esses são os tipos de índices que eu adicionei,

CREATE NONCLUSTERED INDEX IDX1 ON DispatchLink (ContractLink1, DispatchLink1)
CREATE NONCLUSTERED INDEX IDX2 ON DispatchLink (ContractLink2, DispatchLink2)
CREATE NONCLUSTERED INDEX IDX3 ON DispatchLink (ContractLink1, DispatchLink1, ContractLink2, DispatchLink2)

Portanto, ele usa os índices, mas faz uma varredura do índice em todo o índice; portanto, 50000 registros, ele varre 50000 registros no índice.

Pedro
fonte
Você tem algum índice em DispatchLinkcima da mesa?
precisa saber é o seguinte
Eu adicionei os índices que tentei acima.
peter
Na sua consulta: "selecione * de Dispatch d join interno DispatchLink dl em d.DispatchId = dl.DispatchLink1 e d.ContractId = dl.ContractLink1 ou d.DispatchId = dl.DispatchLink2 e d.ContractId = dl.ContractLink2" e tente remover a condição "OR" e substitua-a por UNION de 2 instruções SELECT, cada uma usando "OR", também use as únicas colunas de chave em ambos os SELECTs, em vez do "*", apenas para tornar o teste o mais puro possível.
NoChance
Obrigado SQL Kiwi, isso é algo que eu tentei anteriormente, mas não funcionou infelizmente.
peter
11
É possível que a replicação emita uma consulta mais simples: selecione * em Dispatch d join interno DispatchLink dl em d.DispatchId = dl.DispatchLink1 e d.ContractId = dl.ContractLink1 Se sim, podemos duplicar dados no DispatchLink para que os resultados ainda sejam válidos ... #
676 AK

Respostas:

12

O otimizador pode considerar muitas alternativas de plano (incluindo aquelas com várias buscas), mas para disjunções ( ORpredicados) ele não considera planos que envolvem interseções de índice por padrão. Dados os índices:

CREATE CLUSTERED INDEX cx 
ON dbo.DispatchLink (DispatchLink1, ContractLink1);

CREATE NONCLUSTERED INDEX nc1 
ON dbo.DispatchLink (DispatchLink2, ContractLink2);

Podemos forçar buscas de índice (assumindo o SQL Server 2008 ou posterior):

SELECT * 
FROM dbo.Dispatch AS d
INNER JOIN dbo.DispatchLink AS dl WITH (FORCESEEK) ON 
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Plano FORCESEEK

Usando seus dados de amostra, o plano de busca custa 0,0332551 unidades em comparação com 0,0068057 para o plano de varredura:

Plano de digitalização

Existem todos os tipos de reescritas e dicas possíveis que podemos tentar. Um exemplo de reescrita para promover uma opção que o otimizador não considera para o plano original é:

SELECT * 
FROM dbo.Dispatch AS d
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

Este plano de execução não procura o segundo índice se encontrar uma correspondência no primeiro:

APLICAR TOP Plan

Isso pode ter um desempenho um pouco melhor que o FORCESEEKplano padrão .

Sem adicionar novos índices, também podemos forçar uma busca na tabela Dispatch:

SELECT * 
FROM dbo.DispatchLink AS dl
JOIN dbo.Dispatch AS d WITH (FORCESEEK) ON
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Busca 2

Isso pode ser melhor ou pior que o primeiro exemplo, dependendo de coisas como quantas linhas existem em cada uma das tabelas. A APPLY + TOPmelhoria ainda é possível:

SELECT * 
FROM dbo.DispatchLink AS dl
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;
Paul White 9
fonte
Essa é uma resposta muito útil. Fiz outra pergunta, dba.stackexchange.com/questions/23773/analysing-a-query-plan, que mostra o plano de consulta real em dados reais (não nos meus dados de teste). Não tenho conhecimento para entender exatamente qual é o gargalo no plano de consulta. Talvez você possa dar uma olhada?
peter
É realmente interessante porque adicionar 'FORCESEEK' faz com que minha consulta seja executada em 9 segundos, em vez de levar mais de 10 minutos. Atualizar estatísticas não faz diferença. Por que outro motivo o analisador de consultas estaria errado?
peter
Eu acho que você está certo quanto ao design. O que você quer dizer com repetir colunas? Como você projetaria uma estrutura de tabela que teria que vincular dois registros de expedição como relacionados? Para esclarecer que a tabela 'real' possui seu próprio campo de chave primária, mas sim, ter uma chave composta no Dispatch não ajuda exatamente.
peter
Kiwi SQL. Repetindo colunas. Entendi, obrigado.
peter