Operador do Dia – Compute Scalar
O operador de hoje, é o compute scalar, e ele esta presente na maioria dos planos de execução que vemos.
Como o próprio nome já diz o Compute Scalar efetua um determinado calculo e retorna o valor calculado, este calculo pode ser uma conversão, uma concatenação e outros…
Quase sempre ele é passado despercebido por apresentar um custo mínimo em relação a query, mas ele pode ser um ponto de atenção quando estamos falando de cursores, e loops muito grandes. Principalmente se estiver com problema de CPU.
Por exemplo, utilizar o Compute Scalar para fazer uma simples conversão pode ser tranqüilo, mas execute isso 1 milhão de vezes e verá que se você conseguir evitar este passo terá um ganho de CPU, e por conseqüência terá ganho de tempo.
USE tempdb
GO
CREATE TABLE TABTeste(ID Int Identity(1,1) PRIMARY KEY,
Nome VarChar(250) DEFAULT NewID())
GO
SET NOCOUNT ON
GO
INSERT INTO TABTeste DEFAULT VALUES
GO 10000
O comando acima cria uma tabela e inclui 10000 registros.
Agora vamos fazer um loop para o SQL passar pelo código 1 milhão de vezes.
DECLARE @I Int
SET @I = 0
WHILE @I < 1000000
BEGIN
IF EXISTS(SELECT ID FROM TABTeste WHERE ID = @I)
BEGIN
PRINT ‘Entrou no IF’
END
SET @I = @I + 1;
END
GO
Plano de execução estimado:
Repare que o SQL Server utilizou o Compute Scalar, vamos olhar melhor no plano em modo texto.
|–Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1004] THEN (1) ELSE (0) END))
|–Nested Loops(Left Semi Join, DEFINE:([Expr1004] = [PROBE VALUE]))
|–Constant Scan
|–Clustered Index Seek(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC27096F09E1]), SEEK:([tempdb].[dbo].[TABTeste].[ID]=[@I]) ORDERED FORWARD)
Conforme podemos observar o Compute Scalar foi Utilizado para verificar se o Nested Loop retornou alguma coisa ou não, ou seja, ele está fazendo o papel do IF EXISTS…
O resultado de execução da query e o uso de CPU está na imagem abaixo, gerada pelo Profiler.
Agora vamos alterar o código para remover o uso do Compute Scalar
DECLARE @I Int, @Var Int
SET @I = 0
WHILE @I < 1000000
BEGIN
SELECT @Var = ID FROM TABTeste WHERE ID = @I
IF @@ROWCOUNT > 0
BEGIN
PRINT ‘Entrou no IF’
END
SET @I = @I + 1;
END
GO
Plano de execução estimado:
|–Clustered Index Seek(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC27096F09E1]), SEEK:([tempdb].[dbo].[TABTeste].[ID]=[@I]) ORDERED FORWARD)
Desta vez o SQL não utilizou o Compute Scalar, vamos ver o resultado no profiler.
Como você pode observar o SQL utilizou menos CPU e terminou a execução em nos tempo. Não estou querendo mostrar qual é a melhor forma de fazer uma validação, apenas que exibir o uso do operador. De qualquer forma, fica ai a dica do uso da @@RowCount. Eu já mudei um código onde tinha uns 30 IF EXISTS dentro de um loop desses e o resultado foi bem satisfatório.
Vamos ver mais do Compute Scalar na pratica.
DECLARE @Tab TABLE(ID SmallInt PRIMARY KEY)
SELECT ‘Fabiano’ + ‘ – ‘ + ‘Amorim’ FROM @Tab
SELECT ‘Fabiano’ + ‘ – ‘ + ‘Amorim’ FROM @Tab
|–Compute Scalar(DEFINE:([Expr1003]=’Fabiano – Amorim’))
|–Clustered Index Scan(OBJECT:(@Tab))
O plano gerado foi exatamente para fazer a concatenação, simples não?
Agora vamos ver um comportamento bem interessante do Compute Scalar, que mudou a partir do SQL Server 2005.
Imagine o seguinte código:
DECLARE @Tab TABLE(ID SmallInt PRIMARY KEY)
DECLARE @ID_Int Integer
SELECT *
FROM @Tab
WHERE ID = @ID_Int
Repare que a coluna ID é do tipo SmallInt e a variável @ID_Int é do tipo Integer, ou seja o SQL terá que converter o valor de @ID_Int para poder efetuar a comparação com a coluna ID.
No SQL Server 2000 ele gera o seguinte plano:
SELECT * FROM @Tab WHERE ID = @ID_Int
|–Nested Loops(Inner Join, OUTER REFERENCES:([Expr1002], [Expr1003], [Expr1004]))
|–Compute Scalar(DEFINE:([Expr1002]=Convert([@ID_Int])-1, [Expr1003]=Convert([@ID_Int])+1, [Expr1004]=If (Convert([@ID_Int])-1=NULL) then 0 else 6|If (Convert([@ID_Int])+1=NULL) then 0 else 10))
| |–Constant Scan
|–Clustered Index Seek(OBJECT:(@Tab), SEEK:(@Tab.[ID] > [Expr1002] AND @Tab.[ID] < [Expr1003]), WHERE:(Convert(@Tab.[ID])=[@ID_Int]) ORDERED FORWARD)
Uau, que trampo eim? Agora vamos ver o que acontece se eu rodar este código no SQL 2005 ou 2008.
Plano de execução no SQL Server 2005
SELECT * FROM @Tab WHERE ID = @ID_Int
|–Clustered Index Seek(OBJECT:(@Tab), SEEK:([ID]=[@ID_Int]) ORDERED FORWARD)
Aaaa, agora sim… Ficou fácil não? Ué mas o SQL não precisa mais converter o valor?
Vamos dar uma olhada melhor nos hints do operador Clustered Index Seek.
O time de desenvolvimento do Query Optimizer mudou um pouco a coisa, agora o SQL utiliza a função “Scalar Operator” para converter o valor de @ID_Int para o tipo da coluna ID.
Interessante…
Por hoje é só, até segunda-feira com o próximo Operador do dia.