Tenho a seguinte tabela e dados:
CREATE TABLE myTable (
ID INT IDENTITY(1,1) PRIMARY KEY,
Column1 VARCHAR(50),
Column2 VARCHAR(50),
Column3 VARCHAR(50),
Column4 VARCHAR(50),
Column5 VARCHAR(50),
Column6 VARCHAR(50),
Column7 VARCHAR(50),
Column8 VARCHAR(50),
Column9 VARCHAR(50),
Column10 VARCHAR(50)
)
DECLARE @i INT = 1
DECLARE @j INT = 1
DECLARE @distinct_value_count INT = 20
DECLARE @distinct_value_count_with_more_rows INT = 3
DECLARE @rows_per_distinct_value INT = (20000 - (@distinct_value_count_with_more_rows * 2000)) / (@distinct_value_count - @distinct_value_count_with_more_rows)
WHILE @i <= @distinct_value_count
BEGIN
DECLARE @current_rows_per_value INT = @rows_per_distinct_value
IF @i <= @distinct_value_count_with_more_rows
BEGIN
SET @current_rows_per_value = @rows_per_distinct_value + 2000
END
SET @j = 1
WHILE @j <= @current_rows_per_value
BEGIN
INSERT INTO myTable (Column1, Column2, Column3, Column4, Column5, Column6, Column7, Column8, Column9, Column10)
VALUES ('Value' + CAST(@i AS VARCHAR(2)),
'Value' + CAST(@j AS VARCHAR(5)),
'Value' + CAST(@j + 1 AS VARCHAR(5)),
'Value' + CAST(@j + 2 AS VARCHAR(5)),
'Value' + CAST(@j + 3 AS VARCHAR(5)),
'Value' + CAST(@j + 4 AS VARCHAR(5)),
'Value' + CAST(@j + 5 AS VARCHAR(5)),
'Value' + CAST(@j + 6 AS VARCHAR(5)),
'Value' + CAST(@j + 7 AS VARCHAR(5)),
'Value' + CAST(@j + 8 AS VARCHAR(5)))
SET @j = @j + 1
END
SET @i = @i + 1
END
Alter Table dbo.myTable
Add Column11 varchar(50), Column12 varchar(50)
Alter Table dbo.myTable
Add dateModified datetime
Update dbo.myTable
set Column11 = Column1
,Column12 = Column1
Update Top (10) dbo.myTable
Set Column11 = 'Value7'
Where Column1 = 'Value1'
Update Top (10) dbo.myTable
Set Column12 = 'Value7'
Where Column1 = 'Value1'
Update Top (10) dbo.myTable
Set Column11 = 'Value6'
Where Column1 = 'Value1'
Update Top (10) dbo.myTable
Set Column12 = 'Value6'
Where Column1 = 'Value1'
Update Top (10) dbo.myTable
Set Column11 = 'Value5'
Where Column1 = 'Value1'
Update Top (10) dbo.myTable
Set Column12 = 'Value5'
Where Column1 = 'Value1'
Update dbo.myTable
set dateModified = getdate() + ID
CREATE NONCLUSTERED INDEX [Idx_col] ON [dbo].[myTable]
(
[Column1] ASC,
[Column11] ASC,
[Column12] ASC,
[dateModified] ASC
)
INCLUDE([Column5],[Column6])
Tenho que filtrar com base em algumas colunas e retornar todas as colunas da tabela. Para fazer isso, tenho um índice que cobre as colunas que precisam ser filtradas. Estou dividindo a consulta em duas partes:
Obtenha todas as linhas de chave primária que satisfaçam o filtro e armazene-as em uma tabela temporária. Essa consulta usa o índice não clusterizado.
Junte esta tabela temporária de volta à tabela principal na coluna de chave primária para que o índice clusterizado seja usado para obter todas as colunas.
No entanto, estou enfrentando um problema quando tento fazer isso. No primeiro cenário, estou obtendo todas as linhas filtradas em uma tabela temporária e, quando a incluo de volta na tabela principal, está fazendo uma varredura de índice clusterizado. No segundo cenário, estou obtendo apenas as 50 primeiras linhas na tabela temporária e, quando as incluo na tabela principal, está fazendo uma busca de índice clusterizado. Estou confuso sobre por que isso está acontecendo. Em ambos os casos, não há índice na tabela temporária. Agradeço se alguém puder me ajudar a entender o que está acontecendo. Obrigado!
Cenário 1:
SELECT id
INTO #tmpId
FROM myTable
WHERE Column1= 'Value1'
AND Column11 In( 'Value1','Value5','Value6', 'Value7')
And Column12 In ('Value1','Value6')
And dateModified > dateAdd(day,-5, getdate())
SELECT *
FROM myTable m
JOIN #tmpId t
ON m.id = t.id
drop table if exists #tmpId
Plano de Execução Cenário 1: https://www.brentozar.com/pastetheplan/?id=rkDAD-aLh
Cenário 2:
SELECT id
INTO #tmpId
FROM myTable
WHERE Column1= 'Value1'
AND Column11 In( 'Value1','Value5','Value6', 'Value7')
And Column12 In ('Value1','Value6')
And dateModified > dateAdd(day,-5, getdate())
Order by dateModified desc offset 0 rows fetch next 50 rows only
SELECT *
FROM myTable m
JOIN #tmpId t
ON m.id = t.id
drop table if exists #tmpId
Plano de Execução do Cenário 2: https://www.brentozar.com/pastetheplan/?id=rJVbuWaLh
Como Andy mencionou, não há nada de errado com os planos de execução que você forneceu. O SQL Engine está fazendo seu trabalho conforme o esperado. Diferentes operações são mais eficientes e rápidas, dependendo do tamanho dos dados que estão sendo processados. Index Seeks são proficientes para procurar quantidades menores de dados, enquanto Index Scans são geralmente mais proficientes para grandes quantidades de dados.
Pense em um índice como uma lista telefônica. Os nomes são os dados e são classificados por,
LastName
em seguida, porFirstName
. Se você precisar procurar o número de telefone de uma única pessoa -John Smith
por exemplo, a maneira mais rápida seria pular diretamente para a página comS...
LastNames
e pular diretamente para ondeJohn
está nessa página. Portanto, o equivalente a uma operação Index Seek.Agora, para um cenário diferente, digamos que você precise procurar o número de telefone de cada pessoa. Você pode pular arbitrariamente para a página 'S...' e depois pular para
John
, depois pular para aB
página e pular paraMary
, e então pular para aG
página e pular paraTom
e depois pular de volta para aS
página e pular paraRalph
. Todo esse salto, em grandes quantidades, tem sobrecarga. Como você sabe que precisa ler todos os números de telefone na lista telefônica de qualquer maneira, seria mais rápido (menos sobrecarga) apenas começar pelo primeiro nome na primeira página e ler sequencialmente a lista telefônica até chegar ao sobrenome na última página. Isso seria o equivalente a uma operação Index Scan.Isso é essencialmente o que suas duas consultas e seus planos de execução estão fazendo.
O Mecanismo SQL tem algo chamado de ponto de inflexão que ele usa como um limite para decidir entre quando uma varredura de índice seria mais eficiente ou uma busca de índice, com base na cardinalidade dos dados que estão sendo pesquisados. Mas é um algoritmo complexo que não pode ser calculado definitivamente como um valor estático , então não se preocupe em tentar descobrir o que é. O SQL Engine geralmente sabe melhor de qualquer maneira.
Não há nada para ajudar aqui, como mencionado anteriormente, o SQL Engine está funcionando corretamente para fornecer a você os planos mais eficientes que julgar necessários. Mas se a sua pergunta é mais se isso mudaria alguma coisa, a resposta é não, não é provável.
As escolhas que ele faz, com o algoritmo Tipping Point, são baseadas no número de linhas que precisam ser procuradas. Esse número não mudará apenas porque você adicionou um índice clusterizado à sua tabela temporária.
A busca de índice é benéfica para recuperar uma quantidade relativamente pequena de dados. E pode desacelerar drasticamente uma consulta que recupera um grande número de linhas. Às vezes, o uso inapropriado de busca de índice pode retardar uma consulta por horas. É por isso que as informações sobre o número de linhas a serem selecionadas são cruciais para a otimização da consulta.
Quando você seleciona 50 linhas, o índice de busca funciona perfeitamente. Mas parece que seus dados de 5 dias são muito grandes para que a busca de índice seja mais rápida do que a varredura de índice. É por isso que o otimizador usa a varredura de índice desta vez.
Você também pode tentar usar as dicas do otimizador FORCESEEK e FORCESCAN para monitorar a diferença.