Archive

Articles taggués ‘Optimisation’

Optimisation procédure stockée paramétrée sous SQL Server (optimisation reporting Services)

Je vous propose ici un retour d’expérience sur l’optimisation de procédures stockées SQL server. Cette optimisation est issue de recherche effectuée pour analyser des problèmes de lenteur sur des rapports Reporting Services utilisant des procédures stockées avec passage de paramètres.

Le cas m’est apparu en constatant que les temps d’exécution d’une même requête SQL pouvaient varier de 8s à plus de 10mn selon que je l’exécutais directement dans l’éditeur management Studio ou incorporée à une procédure stockée.

Ce problème est appelé « parameter sniffing ou spoofing». En synthèse, le moteur SQL Server essaye d’optimiser la requête en définissant un plan d’exécution basé sur les statistiques. Ces dernières sont établies lors de la première exécution de la requête et sont donc dépendantes de la valeur du paramètre utilisée pour cette exécution.

Ce plan d’exécution pose problème dès que la distribution des données est biaisée : certains paramètres vont retourner un petit nombre de lignes tandis que d’autres vont au contraire en renvoyer un grand nombre.

Le plan d’exécution n’étant pas recalculé à chaque fois, les temps ne sont plus optimisés.

Ci-dessous un exemple (source http://blogs.msdn.com/b/queryoptteam/archive/2006/03/31/565991.aspx) afin d’expliquer avec plus de détails les points précédents :

Soit les objets suivants :

Une table t de 1002000 lignes avec 100000 lignes allant de 1 à 999999 et 2000 autres lignes avec la valeur 1000008.

Col1

1

1

2

2

1000000

999999

1000001

1000008

1000008

1002000

1000008

Soit la procédure stockée ci-joint interrogeant la table t.

CREATE procedure [dbo].[foo]

(@p int)

as

select * from t where col1 = @p

Une première analyse du plan d’exécution de la procédure stockée avec le paramètre ‘450’ nous donne les informations suivantes :

image

Il est estimé que le nombre de ligne est de 1 et le nombre de ligne renvoyé est aussi de 1.

Dans la deuxième exécution nous allons mettre le paramètre suivant : ‘1000008’ (qui représente 2000 lignes dans la base de données) et nous allons observer le plan d’exécution :

image

Le plan d’exécution estime que le nombre de lignes renvoyées est de 1 alors que le nombre de lignes retournées réellement est de 2000.

Ce plan d’exécution n’est pas optimisé pour le paramètre d’une valeur de ‘10000008’.

En effet le précédent paramètre fourni, d’une valeur de ‘450’, a été aspiré par l’optimiseur SQL et gardé en mémoire ; d’où la mauvaise estimation.

Pour remédier à ce problème et ainsi obtenir de meilleures performances, plusieurs solutions existent : la déclaration d’un paramètre en local (ainsi le paramètre ne peut être aspiré par l’optimiseur de requête SQL) ou l’ajout d’une option ‘‘recompile’’.

Exemple :

Déclaration d’une variable locale

alter procedure [dbo].[foo]

(@p int)

as

declare @p_local int

set @p_local=@p

select * from t where col1 = @p_local

@p=5 image

@p=1000008

image

Dans le premier cas en déclarant une variable locale, le nombre estimé passe à 1,11332 lignes, qui correspondent au calcul suivant : [1/1000001 (nombre de valeur distinct)]*1002000(nombre total de lignes).

Cette première solution utilise les statistiques de la table pour estimer le nombre de lignes. Elle présente cependant un inconvénient quand les données ne sont pas réparties équitablement.

Option (recompile)

alter procedure [dbo].[foo] (@p int)

as

select * from t where col1 = @p

option(recompile)

@p=5 image

@p=1000008

image

Dans le deuxième cas, en utilisant l’option recompile, nous forçons le moteur à recompiler la requête SQL, ce qui peut être contraignant en terme de temps processeur si celle-ci est complexe, mais le plan d’exécution obtenu est toujours optimisé pour le paramètre aspiré.

Dans notre cas l’estimation du nombre de lignes correspond au résultat attendu.

Bilan en termes de performances :

Défaut Variable locale Option recompile
@p=5 91 ms 89 ms 87 ms
@p=1000008 288 ms 255 ms 166 ms

Ci-dessus les temps écoulés lors de l’exécution de la procédure stockée. Dans le cas par défaut le paramètre @p=5 a été aspiré, ce qui induit un temps d’exécution de 288 ms pour le paramètre @p=1000008.

En comparaison, le fait de déclarer une variable locale ou de mettre en place une option recompile ne change pas les temps de traitement pour le paramètre @p=5, les nombres de lignes estimés étant proches, les temps sont quasi-identiques. Cependant, une différence est notable avec le paramètre @p=1000008 : avec l’option recompile, la procédure stockée possède un plan d’exécution optimisé, d’où une meilleur performance. Dans le cas de la déclaration en locale d’une variable avec @p=1000008, le plan étant optimisé à l’aide des statistiques (estimation de 1,1 lignes par résultat) le temps d’exécution reste important face à l’option recompile mais préférable à celui par défaut.

En conclusion, en fonction des cas de figure rencontrés (données biaisées, procédure stockée complexe …), il est préférable d’utiliser l’une des solutions proposées.

SQL-Server : Comment évaluer la qualité d’indexes et faire les REBUILD et REORGANIZE en conséquence ?

Afin de profiter pleinement de l’optimisation des temps de réponses que procurent les indexes, il ne faut pas oublier de prévoir une tâche régulière d’ »entretien ». Elle consiste à effectuer des REBUILD ou des REORGANIZE en fonction de l’état de fragmentation de l’indexe. Cette tâche pourra ensuite être intégrée à un job de chargement quotidien par exemple.

Voici un récapitulatif des « best practises » en la matière.

Astuce

Utiliser la fonction ‘sys.dm_db_index_physical_stats‘ et son champ ‘avg_fragmentation_in_percent

Syntaxe

sys.dm_db_index_physical_stats (
{ database_id | NULL }
, { object_id | NULL }
, { index_id | NULL | 0 }
, { partition_number | NULL }
, { mode | NULL | DEFAULT }
)

Exemple

SELECT b.name, a.* FROM
sys.dm_db_index_physical_stats (
DB_ID(N’Ma_base’)
, OBJECT_ID(N’dbo.Ma_table’)
, NULL
, NULL
, DEFAULT
) AS a
JOIN sys.indexes AS b ON a.object_id = b.object_id AND a.index_id = b.index_id

Recommendations Microsoft

Valeur de avg_fragmentation_in_percent Correction à apporter
> 5% et < = 30% ALTER INDEX REORGANIZE
> 30% ALTER INDEX REBUILD WITH (ONLINE = ON)

Exemples

ALTER INDEX [Mon_indexe] ON dbo.Ma_table REBUILD
ALTER INDEX [Mon_indexe] ON dbo.Ma_table REORGANIZE