Uma constraint ajuda muita gente, mas será que um índice ajuda muito mais?

Há uns dias, em conversa com colegas sobre o meu post em que recomendava o uso de constraints, surgiu uma pergunta pertinente:

“Então não bastava termos usado uns índices e o assunto ficaria resolvido?”

A minha resposta foi “Não. Mas não te fies na minha resposta, testa e vê com os teus próprios olhos, ou melhor ainda, com o teu management studio.”

 

Vamos lá revisitar o código do outro post com algumas melhorias para tirarmos as dúvidas.

Começamos pelas tais tabelas que só interessam para demos e a vista a juntar tudo

CREATE TABLE [dbo].[Dates2015](
	id bigint primary key identity(1,1) not null
,	dt_ref date not null
,	TextCol nvarchar(50) not null
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Dates2016](
	id bigint primary key identity(1,1) not null
,	dt_ref date not null
,	TextCol nvarchar(50) not null
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Dates2017](
	id bigint primary key identity(1,1) not null
,	dt_ref date not null
,	TextCol nvarchar(50) not null
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Dates2018](
	id bigint primary key identity(1,1) not null
,	dt_ref date not null
,	TextCol nvarchar(50) not null
) ON [PRIMARY]
GO


CREATE VIEW [dbo].[DatesAll]
AS
	SELECT        *
	FROM 
	dbo.Dates2015 
union all
	SELECT        *
	from 
	dbo.Dates2016 
union all
	SELECT        *
	from 
	dbo.Dates2017 
union all
	SELECT        *
	from 
	dbo.Dates2018 
GO

Introduzimos agora uns valores nas tabelas para que elas não se sintam vazias, desprovidas de realidade…

insert into Dates2015
( Dt_ref, TextCol)
select	DATEADD(day, ROUND(((DATEDIFF(day, '20150101', '20151231')-1) * RAND()), 0), '20150101')
	,	CAST(newid() as varchar(50))
go 10000

insert into Dates2016
( Dt_ref, TextCol)
select	DATEADD(day, ROUND(((DATEDIFF(day, '20160101', '20161231')-1) * RAND()), 0), '20160101')
	,	CAST(newid() as varchar(50))
go 10000

insert into Dates2017
( Dt_ref, TextCol)
select	DATEADD(day, ROUND(((DATEDIFF(day, '20170101', '20171231')-1) * RAND()), 0), '20170101')
	,	CAST(newid() as varchar(50))
go 10000

insert into Dates2018
( Dt_ref, TextCol)
select	DATEADD(day, ROUND(((DATEDIFF(day, '20180101', '20181231')-1) * RAND()), 0), '20180101')
	,	CAST(newid() as varchar(50))
go 10000

Agora vamos lá verificar o estado sem índices nem constraints

set statistics time, io on

select top 100 *
from [dbo].[DatesAll]
where dt_ref >=  '2018-01-05'

Os resultados não interessam, mas o plano de execução e os resultados do tempo e i/o são esclarecedores

Vemos no plano de execução que andamos pelas 4 tabelas

Notamos bem que varremos as 4 tabelas e, de início parece estranho mas se pensarmos bem é o mais natural, faz menos reads na tabela onde sabemos ser a única que tem dados.

Os mais atentos notaram no missing index recomendation na tabela Dates2017, mas realmente ele pede-nos índices nas 3 tabelas onde teve mais problemas. Mais uma machadada para quem gosta de “seguir à risca” o que o optimizer sugere.

Quando há pouco disse que “é o mais natural”… é o mais natural porquê?

Vejamos o que o nosso amigo SQL Server sabe. Tem uma query a uma vista que é a concatenação de 4 tabelas. Não sabe grande coisa sobre essas tabelas. Vai à vista e expande o código, aparece-lhe a primeira tabela que é a Dates2015, varre-a toda à procura de 100 linhas que respondam à pergunta que lhes fizemos. Depois vai à seguinte… e à seguinte… e finalmente encontra uma onde consegue retirar de lá as 100 linhas sem ter que procurar mais.

“Ó António, estás-me a dizer que a ordem das tabelas na vista interessa?” Claro… mas como sempre… nada como experimentarmos com uma query que sabemos ser satisfeita com a primeira tabela da vista.

select top 100 *
from [dbo].[DatesAll]
where dt_ref <=  '2015-10-05'

Não parece nada diferente…

Afinal, a ordem interessa.

Ora aqui está. 4 reads na Dates2015 e não precisou de ir às outras 3 tabelas, apesar de ter pensado nisso aquando da optimização.

E, já que por aqui andamos, vamos fazer um join que sabemos ser impossível.

select *
from Dates2015 a
inner join Dates2016 b
	on a.Dt_ref = b.Dt_ref

Pois é, nós sabemos mas “ele” não sabe, e por isso anda à procura.

E agora vamos ao que interessa neste post, a resposta à pergunta “e com índices, pá”?

create nonclustered index ncidx on Dates2015 (Dt_ref)
create nonclustered index ncidx on Dates2016 (Dt_ref)
create nonclustered index ncidx on Dates2017 (Dt_ref)
create nonclustered index ncidx on Dates2018 (Dt_ref)
select top 100 *
from [dbo].[DatesAll]
where dt_ref >=  '2018-01-05'

Nada de muito óbvio, à parte de agora ser um index seek.

Agora já se vê alguma diferença. Quase não há reads, afinal andamos pelo índice e não pela tabela e ele, apesar de não saber tudo, já sabe procurar melhor e mais rápido.

E com o caso do join impossível? Também melhora?

Infelizmente não há muito que se tenha melhorado. “Não fez nada”, diríamos.

Pois não, não fez, porque o nosso amigo “select * ” não deixou. Havia pura e simplesmente muito trabalho a ir buscar os campos adicionais que não estavam no nosso índice e o optimizer preferiu usar a tabela toda. Bookmark lookups são ainda piores. Haveremos de ver isso em outro post.

Vamos lá repetir isto apenas a ir buscar a data.

select a.dt_ref
from Dates2015 a
inner join Dates2016 b
	on a.Dt_ref = b.Dt_ref

Em vez de um gastador Hash Match temos um Merge Join e mais rapidamente chegamos ao resultado.

E se criarmos as nossas constraints? Aquelas que ajudam muita gente…

alter table [dbo].[Dates2015]
add constraint dt_2015
	check (	dt_ref >= '2015-01-01' 
			and
			dt_ref <= '2015-12-31' )


alter table [dbo].[Dates2016]
add constraint dt_2016
	check (	dt_ref >= '2016-01-01' 
			and
			dt_ref <= '2016-12-31' )

alter table [dbo].[Dates2017]
add constraint dt_2017
	check (	dt_ref >= '2017-01-01' 
			and
			dt_ref <= '2017-12-31' )

alter table [dbo].[Dates2018]
add constraint dt_2018
	check (	dt_ref >= '2018-01-01' 
			and
			dt_ref <= '2018-12-31' )

vamos lá repetir as nossa queries

select top 100 *
from [dbo].[DatesAll]
where dt_ref >=  '2018-01-05'

E aos joins?

select *
from Dates2015 a
inner join Dates2016 b
	on a.Dt_ref = b.Dt_ref

“Então não bastava termos usado uns índices e o assunto ficaria resolvido?”

Não.

Share

You may also like...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *