Quel est l’impact d’un SELECT COUNT(*) sur les IO ?

Lors de prestations d’audit, ou d’optimisation de performances, il m’arrive de tomber sur des requêtes qui lisent des tables système …

Un exemple relativement récent est la lecture de sysindexes pour obtenir le nombre d’enregistrements d’une table plutôt que de faire un bon vieux SELECT (COUNT(*)) sans condition WHERE.

Use AdventureWorks2008
GO
DECLARE @Table nvarchar(100) = N'Production.TransactionHistory'
SELECT name,indid,rows
from sysindexes
WHERE id = object_id(@Table)

Cela fonctionne. Mais rien ne garantit que cette table système va perdurer, remettant en cause votre application. Et c’est bien ce qui devrait arriver, car sysindexes est bien une table sous SQL Server 2000, mais depuis, il s’agit une vue qui n’est présente que pour des raisons de compatibilité. La documentation en ligne est claire, il convient d’utiliser sys.indexes et autres DMV ou Catalog Views …

image

Lorsque j’ai posé la question du pourquoi une telle requête, la réponse à fusé : “Ben pour aller plus vite, plutôt que de lire la table”.

Certes, c’est rapide. Certes, cela n’entraine que 2 lectures dans ce cas précis. J’ai donc posé la question : Quel est l’impact d’un select count(*) ? Réponse immédiate de mon interlocuteur : Un parcours de toute les lignes de la table … Donc un cluster index scan (ou table scan si stocké sous forme de HEAP).

J’ai donc poursuivi en demandant si il avait testé. Non.

Comment SQL Server procède donc pour réaliser ce SELECT COUNT(*) sans clause WHERE ?

SET STATISTICS IO ON
GO
SELECT COUNT(*)
FROM Production.TransactionHistory

image

157 lectures disque … Que se passe t’il pour un SELECT * FROM Production.TransactionHistory cette fois ci ?

image

Le nombre de lectures est différent. Regardons les plans d’exécution :

image

image

Le select COUNT(*) effectue donc un seek sur un index non cluster. Donc pas d’accès aux données, moins de verrouillage. Mais si la table possède plusieurs index non cluster, lequel va donc être utilisé pour exécuter la requête ?

Voici une requête qui nous renseigne sur les index positionnés sur cette table :

DECLARE @Table nvarchar(100) = N'Production.TransactionHistory'
SELECT  i.name,i.type_desc,
		alloc_unit_type_desc,index_depth,
		index_level,page_count,
		record_count,avg_record_size_in_bytes 
FROM       sys.indexes AS i
INNER JOIN sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID(@Table),  NULL, NULL , 'DETAILED') AS ips
	ON ips.object_id = i.object_id and i.index_id = ips.index_id
WHERE i.object_id = object_id(@Table)

image

Il y a donc un index cluster et 2 index non cluster.

En fait SQL Server va privilégier l”index le plus petit, celui qui va couter le moins en lectures … Il s’agit donc de l’index IX_TransactionHistory_ProductID, qui n’a besoin que de 156 pages pour stocker ses informations contre 213 pour l’autre index noncluster. Les plans d’exécution valident cette différence de cout :

image

image

SQL Server va donc privilégier le plus petit index non cluster pour effectuer l’opération de COUNT(*). Cependant, notez que les index filtrés ne seront pas pris en compte pour cette opération.

Le risque de provoquer de nombreux IO avec une opération de COUNT(*) sans clause WHERE est donc relativement limité…

A propos Christophe

Consultant SQL Server Formateur certifié Microsoft MVP SQL Server MCM SQL Server 2008
Cet article, publié dans SQL Server, est tagué , , . Ajoutez ce permalien à vos favoris.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s