Envoyé par
KeepItSimple
...
(au niveau appli on a un timeout sur select * from table where id in (<valueList>) ).
Utiliser l'étoile dans le SELECT :
est la manière la plus simple de plomber les performances d'un SGBDR et d'entretenir des time out....
En effet, le fait de demander systématiquement le retour dans le dataset de toutes les colonnes de la table, conduit a utiliser rarement un index, par le simple fait qu'un index ne contient pas toutes les données de la table.
Or ne pas utiliser l'index revient a balayer toutes les lignes de la table, ce qui prend du temps.
Cela prend d'autant plus de temps, qu'il faut aussi transmettre ces données du serveur SQL à l'application... Rassurez vous du côté du serveur SQL, cela va très vite à envoyer. Il ne s'agit que de lire des octets et les balancer sous forme de trames vers l'application. Mais du côté applicatif c'est beaucoup beaucoup plus lent... Tout simplement parce que l'application doit construire dynamiquement des objets spécifiques à l'affichage ou au traitement des données (l'information est-elle est un nombre ?, un littéral ?, une date ? Dans quelle culture dois-je afficher la date ? Le nombre ?...). Ceci peut d'ailleurs être vérifié en demandant à SQL Server quels sont ses attentes (du temps gaspillé) et vous verrez immédiatement pour ce type de requêtes un nombre incalculable de ASYNC_NETWORK_IO...
Avoir une restriction (filtre) avec un IN :
where id in (<valueList>)
Est le plus sur moyen de ne jamais passer par un index et donc de faire un scan de table. L'opérateur IN est en fait un une série de OR (vous pouvez voir dans le plan de requête que le IN n'existe pas et est systématiquement remplacé par une série de OR...). Malheureusement le OR (où logique) n'est pas "cherchable" (les anglais disent "sargable"), c'est à dire que l'on ne peut pas utiliser un index pour cherches de multiples valeurs. Mais l'optimiseur de SQL Server est assez malin pour contourner la difficulté et récrire votre requête à la volée pour la rendre cherchable.
Prenons un exemple concret... Vous cherchez les valeurs 'A' ou 'B' sans la colonne CODE. Vous écrivez la requête suivante :
SELECT * FROM MaTable WHERE CODE IN ('A', 'B')
Requête non cherchable, qui est traduit en :
1 2 3
| SELECT * FROM MaTable WHERE CODE = 'A'
UNION
SELECT * FROM MaTable WHERE CODE = 'B' |
Ces deux requêtes avant et après l'opérateur UNION deviennent cherchable (on peut utiliser un index), mais l'union a un coût. Mettons que la recherche avec A ou B par rapport à une recherche unitaire de type recherche A coute deux fois plus cher (logique puisque deux recherches) avec un surcout du même ordre pour l'opération UNION. Nous avons donc un coup de 3 fois supérieur au cout de la requête unitaire.
Que se passe t-il avec :
- 3 valeurs dans le IN ? 3 requêtes et 2 union => cout de 5
- Avec 4 ? cout de 7
- Avec 10 ? cout de 19
Au bout d'un moment, l'optimiseur va trouver, de façon logique et purement mathématique, qu'avec un nombre élevé de valeurs dans le IN, cette stratégie de découpe et d'union, coutera plus cher qu'un simple et unique balayage de la table...
Tout ceci pour vous expliquer que l'un des facteurs les plus important pour obtenir de bonnes performances est d'appliquer à la lettre les bonnes pratiques, parmi lesquelles :
- ne jamais faire de SELECT *, mais mettre après le mot clé SELECT uniquement les colonnes dont on a réellement besoin
- écrire des prédicats cherchables (clauses WHERE, HAVING, JOIN/ON) en évitant le OR, le IN, le LIKE %%...
Au passage transformer votre liste de valeurs en une jointure est une chose aisée en utilisant une jointure qui passe par la création d'une table temporaire indexée... Une jointure sur index étant incommensurablement plus rapide qu'une série en OR (fallait la faire celle là !)
A +
PS, je suis en train d'écrire un article pour faire comprendre la notion de time-out et ses pièges...
Partager