Ancora su query e sulla definizione dei parametri (perfomance e piani di esecuzione)

Published 03 marzo 09 07.13 | abenedetti

Supponiamo di avere una “classica” tabella di comuni, qualcosa come:

image

Nell’esempio in questione, poichè i dati verranno utilizzati al 95% con interrogazioni sulla descrizione del comune, è stato impostato l’indice cluster proprio su questa colonna, definita come VARCHAR(50):

create table municipalities
(
id smallint identity(1,1),
idDistrict smallint foreign key references districts(id),
[description] varchar(50),
code char(5), 

constraint pkMunicipalities primary key nonclustered  (id)
)
go 

create clustered index idxMunicipalities on municipalities ([description])
go
 

A questo punto proviamo a fare una ricerca su una parte di testo, ad esempio:

set statistics io on
exec sp_executesql N'select Description 
from municipalities where (Description like @p0 ) 
order by Description'
,N'@p0 varchar(50)',@p0=N'la s%'
set statistics io off
 
Questo il piano di esecuzione prodotto, con una precisa “Index Seek” sull’indice clustered:
image
Queste le statistiche:
Table 'municipalities'. Scan count 1, logical reads 2
 
Adesso proviamo ad analizzare la stessa interrogazione leggermente diversa.
Ovvero a definire il parametro non più VARCHAR(50) ma NVARCHAR(5), ovvero un NVARCHAR dimensionato secondo la lunghezza del testo passato (se avessi richiesto “ancona%” il parametro sarebbe stato dimensionato a 7 caratteri):
 
set statistics io on
exec sp_executesql N'select Description 
from municipalities where (Description like @p0 ) 
order by Description'
,N'@p0 nvarchar(5)',@p0=N'la s%'
set statistics io off

Il piano di esecuzione e le statistiche sono drammaticamente diversi:

image

Table 'municipalities'. Scan count 1, logical reads 41

Siamo passati, di fatto, ad un Clustered Index Scan che, nel nostro esempio, equivale ad un Table Scan (infatti una “select * from municipalities” esegue proprio 41 letture).

Tra le due istruzioni questa è l’unica differenza:

  • @p0 varchar(50)
  • @p0 nvarchar(5)

 

Qual è il motivo che spinge l’ottimizzatore a scegliere una strada completamente diversa?

 

La differenza nella seconda istruzione, analizzando il piano di esecuzione, sta tutta nel predicato, ovvero:

image

CONVERT_IMPLICIT(nvarchar(50),[lxkMonitoring].[dbo].[municipalities].[description],0) like [@p0]

 

La funzione di conversione (CONVER_IMPLICIT), che deve essere applicata alla colonna e non al parametro, fa si che l’indice costruito su questa stessa non possa essere utilizzato.

 

Cosa succederebbe se fossi io a forzare la conversione del parametro nel tipo corretto ( …cast( @p0 as varchar(50))… )?

set statistics io on
exec sp_executesql N'select Description 
from municipalities where (Description like cast( @p0 as varchar(50)) ) 
order by Description'
,N'@p0 nvarchar(5)',@p0=N'la s%'
set statistics io off

L’istruzione sarebbe nuovamente scritta nel modo corretto, infatti:

image

Table 'municipalities'. Scan count 1, logical reads 2

Morale: l’unica strada corretta è quella di utilizzare in maniera giusta i parametri, definendoli come devono essere defniti.

Comments

# NHibernate queries & sql server execution plans « TDD developer said on marzo 10, 2009 09.58 :

Pingback from  NHibernate queries & sql server execution plans « TDD developer

# Il blog di Andrea Benedetti said on marzo 11, 2009 01.25 :

Mi rifaccio ad un mio precedente post (" Ancora su query e sulla definizione dei parametri (perfomance

This Blog

Syndication