Non search arguments no SQL Server

Algumas vezes o SQL Server não utiliza o plano de execução que você estava esperando e executa, por exemplo, um index scan onde era esperado um index seek. Um dos motivos desse comportamento pode ser a falta de índices (claro!), estatísticas desatualizadas ou problemas com a sua consulta, neste post vamos abordar um detalhe relacionado com o último problema.

O SQL Server possui o conceito de search arguments (SARG) que são condições da sua consulta que podem ser utilizadas para restringir o número de registros retornados para futura manipulação pelo plano de execução... Resumindo, é uma cláusula where que pode utilizar um índice para achar poucos registros (evitar um scan). O formato de uma SARG segue o padrão:

<Coluna> <operador_inclusivo> <constante ou variável>

ou

<constante ou variável> <operador_inclusivo> <Coluna>

O problema acontece quando utilizamos cláusulas negativas ou funções para definirmos nossos filtros (non SARG). Quando isso é feito a engine do SQL Server não utiliza os índices (mesmo que existentes e com estatísticas atualizadas) para fazer a consulta e trabalha com um table scan. PERFORMANCE!

Para demonstrar como isso funciona, montei um script com um exemplo baseado em um caso que encontrei nas andanças por esse Brasil. J

CREATE TABLE NonSARG

(

          Data DATETIME NOT NULL,

          Maquina VARCHAR(50) not null,

          Numero decimal null

)

ALTER TABLE NonSARG

ADD CONSTRAINT pk_NonSARG

PRIMARY KEY (Data, Maquina)

GO

DECLARE @Contador INT

DECLARE @Data DATETIME

SET @Contador = 0

SET @Data = '20060404'

WHILE @Contador < 10000

BEGIN

          INSERT INTO NonSarg VALUES (DATEADD(dd, @Contador, @Data), 'Maq' + CAST(@Contador AS VARCHAR(10)), 10)

          set @contador = @contador + 1

END

Go

Esse script cria uma tabela chamada NonSARG e insere 10.000 registros, todos com datas únicas e nomes de máquinas únicas. No query analyzer habilite a opção para exibir o plano de execução (Ctrl + K) e execute a consulta:

select * from NonSARG WHERE Data = '20061229'

|--Clustered Index Seek(OBJECT:([Inside].[dbo].[NonSARG].[pk_NonSARG]), SEEK:([NonSARG].[Data]=Convert([@1])) ORDERED FORWARD)

Como resultado temos 1 registro que foi localizado através de um index seek utilizando o índice PK_NonSarg, como esperado. Porém quando executamos a próxima consulta, a nossa cláusula where deixou de ser um SARG, pois utilizamos a função CONVERT no campo data.

select * from NonSARG WHERE CONVERT(char,Data,111) = '2006/12/29'

|--Clustered Index Scan(OBJECT:([Inside].[dbo].[NonSARG].[pk_NonSARG]), WHERE:(Convert([NonSARG].[Data])='2006/12/29'))

Neste caso toda a tabela é percorrida até que seja encontrado o registro com a data 29/12/2006.

Em uma tabela de exemplo com somente 10.000 registros o impacto disto é muito pequeno, agora se você pegar uma tabela de mais de 10 milhões de registros, com vários campos (= menos registros por página no nível folha do índice clusterizado) e sendo acessada por vários processos, a coisa pode ficar feia.

Imagine agora a quantidade de locks exclusivos se for executado uma instrução do tipo delete ... where convert(...) = ‘2006/12/28’. Égua!! A coisa não fica bonita não.

Portanto na hora de você escrever as suas consultas, preste atenção como estão sendo construídos seus filtros e sempre verifique como é o plano de execução.

Para mais informações sobre isso, leia o Inside SQL Server 2000 J e o artigo https://www.sqlmag.com/articles/index.cfm?articleid=42349&puuid=67C1FAFF-1279-906B-AC6CEF8FB0E24437

Em anexo está o script com o exemplo.

[]s

Luti

nonSARGs.sql