Não me entenda mal – eu amo a propriedade Real Rows Read que vimos chegar nos planos de execução do SQL Server no final de 2015. Mas no SQL Server 2016 SP1, menos de dois meses atrás (e considerando que tivemos o Natal no meio, acho que não conta muito tempo desde então), tivemos outra adição interessante – Número estimado de linhas a serem lidas (oh, e isso se deve um pouco ao item do Connect que enviei, tanto demonstrando que vale a pena enviar os itens do Connect quanto tornando esta postagem elegível para o T-SQL Tuesday deste mês, hospedado por Brent Ozar (@brento) no tópico de itens do Connect ).
Vamos recapitular um momento… quando o SQL Engine acessa dados em uma tabela, ele usa uma operação Scan ou uma operação Seek. E, a menos que o Seek tenha um Predicado de Seek que possa acessar no máximo uma linha (porque está procurando uma correspondência de igualdade em um conjunto de colunas - pode ser apenas uma única coluna - que são conhecidas por serem únicas), então o Seek executará um RangeScan e se comporta como um Scan, apenas no subconjunto de linhas que são satisfeitas pelo Seek Predicate.
As linhas atendidas por um predicado de busca (no caso de RangeScan de uma operação de busca) ou todas as linhas da tabela (no caso de uma operação de varredura) são tratadas essencialmente da mesma maneira. Ambos podem ser encerrados antecipadamente se não forem solicitadas mais linhas do operador à sua esquerda, por exemplo, se um operador Top em algum lugar já tiver obtido linhas suficientes ou se um Operador de mesclagem não tiver mais linhas para corresponder. E ambos podem ser filtrados ainda mais por um Predicate Residual (mostrado como a propriedade 'Predicate') antes que as linhas sejam atendidas pelo operador Scan/Seek. As propriedades “Number of Rows” e “Estimated Number of Rows” nos informavam quantas linhas deveriam ser produzidas pelo operador, mas não tínhamos nenhuma informação sobre como algumas linhas seriam filtradas apenas pelo Predicate Seek. Podíamos ver o TableCardinality, mas isso só era realmente útil para os operadores Scan, onde havia uma chance de que o Scan pudesse procurar em toda a tabela as linhas necessárias. Não foi útil para Seeks.
A consulta que estou executando aqui é no banco de dados WideWorldImporters e é:
SELECT COUNT(*) FROM Sales.Orders WHERE SalespersonPersonID = 7 AND YEAR(OrderDate) = 2013 AND MONTH(OrderDate) = 4;
Além disso, tenho um índice em jogo:
CREATE NONCLUSTERED INDEX rf_Orders_SalesPeople_OrderDate ON Sales.Orders (SalespersonPersonID, OrderDate);
Esse índice cobre – a consulta não precisa de outras colunas para obter sua resposta – e foi projetado para que um Predicado de busca possa ser usado em SalespersonPersonID, filtrando rapidamente os dados para um intervalo menor. As funções em OrderDate significam que esses dois últimos predicados não podem ser usados no Seek Predicate, então eles são relegados ao Residual Predicate. Uma consulta melhor filtraria essas datas usando OrderDate>='20130401' AND OrderDate <'20130501', mas estou imaginando um cenário aqui que é muito comum…
Agora, se eu executar a consulta, posso ver o impacto dos Predicados Residuais. O Plan Explorer até dá aquele aviso útil sobre o qual eu escrevi antes.
Posso ver muito claramente que o RangeScan tem 7.276 linhas e que o Predicado Residual filtra isso para 149. O Plan Explorer mostra mais informações sobre isso na dica de ferramenta:
Mas sem executar a consulta, não consigo ver essa informação. Simplesmente não está lá. Os imóveis do plano estimado não possuem:
E tenho certeza de que não preciso lembrá-lo – essa informação também não está presente no cache do plano. Tendo pegado o plano do cache usando:
SELECT p.query_plan, t.text FROM sys.dm_exec_cached_plans c CROSS APPLY sys.dm_exec_query_plan(c.plan_handle) p CROSS APPLY sys.dm_exec_sql_text(c.plan_handle) t WHERE t.text LIKE '%YEAR%';
Eu o abri e, com certeza, nenhum sinal desse valor de 7.276. Parece exatamente o mesmo que o plano estimado que acabei de mostrar.
Tirar os planos do cache é onde os valores estimados se destacam. Não é apenas que eu prefira não executar consultas potencialmente caras em bancos de dados de clientes. Consultar o cache do plano é uma coisa, mas executar consultas para obter os dados reais – isso é muito mais difícil.
Com o SQL 2016 SP1 instalado, graças a esse item Connect, agora posso ver a propriedade Estimated Number of Rows to be Read nos planos estimados e no cache do plano. A dica de ferramenta do operador mostrada aqui é retirada do cache e posso ver facilmente a propriedade Estimated mostrando 7.276, bem como o aviso residual:
Isso é algo que eu poderia fazer em uma caixa de cliente, procurando no cache por situações em planos problemáticos em que a proporção entre o número estimado de linhas a serem lidas e o número estimado de linhas não é grande. Potencialmente, alguém poderia fazer um processo que verificasse todos os planos no cache, mas não é algo que eu fiz.
Uma leitura astuta terá notado que as Linhas Reais que saíram desse operador foram 149, o que era muito menor do que o estimado 1.382,56. Mas quando estou procurando por Predicados Residuais que precisam verificar muitas linhas, a proporção de 1.382,56 :7.276 ainda é significativa.
Agora que descobrimos que essa consulta é ineficaz sem precisar executá-la, a maneira de corrigi-la é garantir que o Predicado Residual seja suficientemente SARGable. Esta consulta…
SELECT COUNT(*) FROM Sales.Orders WHERE SalespersonPersonID = 7 AND OrderDate >= '20130401' AND OrderDate < '20130501';
… dá os mesmos resultados e não tem um Predicado Residual. Nesta situação, o valor Estimated Number of Rows to be Read é idêntico ao Estimated Number of Rows e a ineficiência desaparece:
Como mencionado anteriormente, este post faz parte do T-SQL Tuesday deste mês. Por que não ir até lá para ver quais outras solicitações de recursos foram concedidas recentemente?