SQL Select demorando muito para ser executado

9

É uma seleção simples de uma tabela temporária, que permanece unida a uma tabela existente em sua chave primária, com duas sub-seleções usando o primeiro 1 referente à tabela unida.

Em código:

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn1 AND 
    TempTable.PKColumn2 = JoinedTable.PKColumn2)
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Esta é uma réplica exata da minha consulta.

Se eu remover as duas sub-seleções, ele funcionará bem e rapidamente. Com as duas sub-seleções, recebo cerca de 100 registros por segundo, o que é extremamente lento para esta consulta porque ela deve retornar quase um milhão de registros.

Eu verifiquei se todas as tabelas possuem uma chave primária, todas elas possuem. Todos eles têm índices E estatísticas para suas colunas importantes, como aquelas nessas cláusulas WHERE e as da cláusula JOIN. A única tabela sem chave primária definida nem índice é a tabela temporária, mas também não é o problema porque não é a que está relacionada às sub-seleções lentas e, como mencionei, sem sub-seleções, ela funciona muito bem.

Sem eles TOP 1, retorna mais de um resultado e gera um erro.

Ajuda alguém?

EDIT :

Então, o plano de execução me disse que estava faltando um índice. Eu criei e recriei alguns dos outros índices. Depois de um tempo, o plano de execução os estava usando e a consulta agora é rápida. O único problema é que não estou conseguindo fazer isso novamente em outro servidor, para a mesma consulta. Portanto, minha solução será sugerir qual índice o SQL Server usará.

Smur
fonte
Uau, isso é impressionante. Mas você pode dividir isso em várias instruções separadas? Além disso, que tal procedimentos armazenados?
2
@Adel Essa seleção é de fato uma sub-seleção dentro de um Procedimento Armazenado. A coisa toda é bastante grande, na verdade, mas tenho 100% de certeza de que essa é a parte exata que está demorando para ser executada.
A mudança no plano de execução, incluindo os índices selecionados automaticamente, provavelmente tem a ver com uma alteração nos dados. Eu asseguraria que seus índices cobrissem completamente ou o mecanismo seguirá caminhos inesperados, como uma verificação de tabela. Sugiro revisar o plano de execução no novo servidor (sem dicas) para ver onde você está tendo desvios do sistema original.
Robert Miller
Eu vejo. Eu mudei apenas o servidor, o banco de dados é o mesmo, com os mesmos índices. Ainda assim, parece que não optou automaticamente por usar meus índices. Ele faz exatamente o que você disse: uma verificação de tabela.
Smur
Parece que o opitmizer da consulta não gosta de nenhum índice da tabela para sua consulta. O plano de execução mostrou um índice ausente?
Robert Miller

Respostas:

7

Eu acho que em uma consulta de um milhão de registros, você precisa evitar coisas assim OUTER JOINS. Eu sugiro que você use em UNION ALLvez de LEFT JOIN. Contanto que eu ache CROSS APPLYmais eficiente que a subconsulta na cláusula select, modificarei a consulta escrita por Conard Frix, que acho correta.

agora: quando comecei a modificar sua consulta notei que você tem uma cláusula WHERE dizendo: JoinedTable.WhereColumn IN (1, 3). nesse caso, se o campo for nulo, a condição se tornará falsa. então por que você está usando LEFT JOIN enquanto filtra linhas com valor nulo? basta substituir LEFT JOINCom INNER JOIN, eu garanto que ele vai se tornar mais rápido.

sobre INDEX:

observe que, quando você tiver um índice em uma tabela, diga

table1(a int, b nvarchar)

e seu índice é:

nonclustered index ix1 on table1(a)

e você quer fazer algo assim:

select a,b from table1
where a < 10

no seu índice, você não incluiu a coluna. O bque acontece?

se o sql-server usar seu índice, ele terá que pesquisar no índice, chamado "Busca de Índice" e, em seguida, consultar a tabela principal para obter a coluna b, chamada "Pesquisar" . Este procedimento pode levar muito mais tempo do que digitalizar a tabela em si: "Digitalização de tabela" .

mas com base nas estatísticas que o sql-server possui, nessas situações, ele pode não usar seu índice.

portanto, verifique primeiro Execution Planse o índice é usado.

se sim ou não, altere seu índice para incluir todas as colunas que você está selecionando. diga como:

nonclustered index ix1 on table1(a) include(b)

nesse caso, a pesquisa não será necessária e sua consulta será executada muito mais rapidamente.


fonte
11
Não posso alterar a união esquerda para a união interna, isso estragaria os resultados, é uma regra de negócios: a segunda tabela não precisa necessariamente ter um registro relacionado. Além disso, a coluna na cláusula WHERE não aceita valores nulos.
Smur
6

É o submarino na seleção da coluna que está causando o retorno lento. Você deve tentar usar suas sub-seleções nas junções esquerdas ou usar uma tabela derivada conforme definido abaixo.

Usando associações à esquerda em duas instâncias da terceira tabela

SELECT
  TempTable.Col1,
  TempTable.Col2,
  TempTable.Col3,
  JoinedTable.Col1,
  JoinedTable.Col2,
  ThirdTable.Col1 AS ThirdTableColumn1,
  ThirdTable2.Col1 AS ThirdTableColumn2
FROM #TempTable as TempTable
LEFT JOIN JoinedTable ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTable.PKColumn2)
LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Usando uma tabela derivada

 SELECT 
      TempTable.Col1,
      TempTable.Col2,
      TempTable.Col3,
      DerivedTable.Col1,
      DerivedTable.Col2,
      DerivedTable.ThirdTableColumn1,
      DerivedTable.ThirdTableColumn2
 FROM #TempTable as TempTable
    LEFT JOIN (SELECT
                 JoinedTable.PKColumn2,
                 JoinedTable.Col1,
                 JoinedTable.Col2,
                 JoinedTable.WhereColumn,
                 ThirdTable.Col1 AS ThirdTableColumn1,
                 ThirdTable2.Col1 AS ThirdTableColumn2
               FROM JoinedTable
               LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
               LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn) 
        DerivedTable ON (TempTable.PKColumn1 = DerivedTable .PKColumn2 AND 
        TempTable.PKColumn2 = DerivedTable.PKColumn2)
    WHERE
        DerivedTable.WhereColumn IN  (1, 3)
John Hartsock
fonte
2

Tente aplicar uma cruz

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    ThirdTableColumn1.col1,
    ThirdTableColumn2.col1

FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTablePKColumn2)

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Você também pode usar CTEs e row_number ou uma consulta embutida usando MIN

Conrad Frix
fonte
2

Mova os bits JOIN para fora da parte principal da cláusula e coloque-a como uma subseleção. Movê-lo para a seção WHERE e JOIN garante que você não precise SELECIONAR TOP 1 repetidamente, o que acredito ser o motivo da lentidão. Se você quiser verificar isso, examine o plano de execução.


fonte
2

As ThirdTablereferências (sub-selecionadas no seu exemplo) precisam da mesma atenção do índice que qualquer outra parte de uma consulta.

Independentemente de você usar sub-seleções:

(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeColumn = JoinedTable.SomeColumn
) as ThirdTableColumn1,
(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
) as ThirdTableColumn2,

LEFT JOINS (como proposto por John Hartsock):

LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn

CROSS APPLY (como proposto por Conrad Frix):

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2

Você precisa se certificar covering indexessão definidos para ThirdTable.SomeColumne ThirdTable.SomeOtherColumne os índices são únicos. Isso significa que você precisará qualificar ainda mais as ThirdTablereferências para eliminar a seleção de várias linhas e melhorar o desempenho. A escolha de sub selects, LEFT JOINou CROSS APPLYnão será realmente importante até que você melhore a seletividade ThirdTable.SomeColumne ThirdTable.SomeOtherColumninclua mais colunas para garantir uma seletividade exclusiva. Até lá, espero que seu desempenho continue sofrendo.

O covering indextópico é bem apresentado por Maziar Taheri; Embora não repita seu trabalho, enfatizo a necessidade de levar a sério o uso de índices de cobertura.

Em suma: melhorar a selectividade para o ThirdTable.SomeColumne ThirdTable.SomeOtherColumnconsultas (ou une) adicionando relacionado colunas na tabela para garantir uma correspondência de linha única. Se isso não for possível, você continuará sofrendo problemas de desempenho, pois o mecanismo está ocupado puxando as linhas que são jogadas fora. Isso afeta seu I / O, CPU e, finalmente, o plano de execução.

Robert Miller
fonte