Oi! Seja bem-vindo!

Hoje vamos comparar a performance das 5 formas de gerar números sequenciais mais populares do mercado. Além de aprender como gerar sequências numéricas, nesse artigo você verá como comparar o tempo de execução e o custo computacional de códigos SQL.

 

 

Como medir o tempo de execução de códigos SQL

Cronometro

Uma das medidas mais diretas de performance é o tempo de execução de um determinado código. Para medir isso basta começar salvando a data hora em uma variável datetime, e após a execução do código, demonstrar o tempo transcorrido, diminuindo a data hora atual da data hora salva na variável:


-- Salvar data hora na variável @cronometro
declare @cronometro datetime = getdate()

-- Executar o código que você deseja medir
;with cte as (
	select 1 as sequencia
	union all
	select sequencia + 1 as sequencia from cte where sequencia < 100000
)
select * from cte option (maxrecursion 0)

-- Demonstrar o tempo transcorrido
print convert(varchar, getdate() - @cronometro, 114)
go

/*
Resultado:
(100000 row(s) affected)
00:00:00:830
*/

 

Plano de Execução

Outra medida efetiva de performance, é o custo relativo de execução. Quem gera essa informação é o próprio SQL, e para determinarmos que ele deve fazer isso, precisamos ativar a geração através da tecla de atalho “CONTROL + M”, ou então do menu “Query -> Include Actual Execution Plan”.

 

Ativado o plano de execução e rodando o script abaixo:

use master
select * from sys.objects
select * from sys.tables

Você terá como resultado uma nova aba chamada “Execution Plan”, que demonstrará o custo relativo dos comandos executados de forma similar a essa imagem:

exemplo de custo relativo de uma query

A informação que nos interessa aqui é o custo relativo de cada consulta em relação ao BATCH (todo o script executado). No exemplo acima, 30% do custo computacional foi gasto com “select * from sys.objects” e 70% com “select * from sys.tables”.

 

5 formas de gerar números sequenciais

Agora que já sabemos comparar a performance, vamos as metodologias para gerar números sequenciais. Em minha experiência, as 5 formas que testaremos a seguir, são as mais populares. Caso você não conheça alguma delas, no vídeo expliquei em detalhes como cada uma delas funciona.

 

Para compara-las lado a lado, desligue a demonstração do plano de execução e rode o script abaixo:


------------------------------------------------------------------
-- Performance na geração de números sequencias
------------------------------------------------------------------
-- Observação: Ativar o Execution Plan: Control + M

--------------------------------------------------
-- Loop tradicional
--------------------------------------------------
set nocount on
--/*
declare @sequencia int = 1
declare @retorno table (numero int)
declare @cronometro datetime = getdate()
while @sequencia <= 100000
	begin
		insert into @retorno values (@sequencia)
		set @sequencia = @sequencia + 1
	end
select * from @retorno
print 'WHILE: ' + convert(varchar, getdate() - @cronometro, 114)
go
--*/

--------------------------------------------------
-- CTE recursiva
--------------------------------------------------
declare @cronometro datetime = getdate()
;with cte as (
	select 1 as sequencia
	union all
	select sequencia + 1 as sequencia from cte where sequencia < 100000
)
select * from cte option (maxrecursion 0)
print 'CTE Recursiva: ' + convert(varchar, getdate() - @cronometro, 114)
go

--------------------------------------------------
-- CTEs Agrupadas
--------------------------------------------------
declare @cronometro datetime = getdate()
;with 
	cte_ancora (sequencia) as ( -- gera 10 números 1
			select 1 union all select 1 union all select 1 union all select 1 union all select 1 
			union all 
			select 1 union all select 1 union all select 1 union all select 1 union all select 1
	),
	cte_sequencia (sequencia) as ( -- usa o cross join para combinar todos os registros de um resultset com todos os registros de outro resultset
		select 1 from cte_ancora
		cross join cte_ancora as [10*10=100]
		cross join cte_ancora as [100*10=1K]
		cross join cte_ancora as [1000*10=10K]
		cross join cte_ancora as [10000*10=100K])
select 
	row_number() over (order by (select null)) linha
from cte_sequencia
print 'CTEs Agrupadas: ' + convert(varchar, getdate() - @cronometro, 114)
go



--------------------------------------------------
-- Row constructor
--------------------------------------------------
declare @cronometro datetime = getdate()
select 
	1 + ate9.n + 10*ate99.n + 100*ate999.n + 1000*ate9999.n + 10000*ate99999.n as sequencia
from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ate9(n), -- até 9
     (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ate99(n), -- até 99
     (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ate999(n), -- até 999
     (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ate9999(n), -- até 9999
	 (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ate99999(n) -- até 99999
order by 1
print 'Row Constructor: ' + convert(varchar, getdate() - @cronometro, 114)
go

--------------------------------------------------
-- Select em tabelas de sistema
--------------------------------------------------
declare @cronometro datetime = getdate()
select top (100000) 
	row_number() over (order by (select null)) sequencia
FROM sys.all_objects a
cross join sys.all_objects b
print 'Select sys.all_objects: ' + convert(varchar, getdate() - @cronometro, 114)
go

 

Resultados do cronometro

Após inúmeras execuções, percebi que os resultados dos 3 primeiros colocados variam. A quarta e quinta posição não. Em minha última execução ficou assim:

técnica tempo
Select sys.all_objects 00:00:00:567
CTEs Agrupadas 00:00:00:570
Row Constructor 00:00:00:670
CTE Recursiva 00:00:00:843
WHILE 00:00:02:880

Resultados do custo relativo

Para comparar o custo computacional relativo, ligue o plano de execução (atalho CONTROL+M, como vimos acima). Comente o código que gera a lista de números via WHILE, pois para 100 mil interações ele provavelmente travará sua máquina.

Diferentemente do que ocorreu com os tempos de execução, o custo relativo se mostrou estável, apresentando constantemente o resultado abaixo:

técnica Custo relativo
CTE Recursiva 0%
Select sys.all_objects 8%
CTEs Agrupadas 10%
Row Constructor 82%
WHILE N/A

 

CONCLUSÃO

Achei peculiar o tempo de execução não bater com o custo relativo porque em geral são correlacionados.

Considerando as duas medidas, possivelmente o vencedor seja o “SELECT * from SYSOBJECTS”. O que você acha?

Abraço do seu amigo Josué 🙂

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *