Comment se faire haïr de son hébergeur en quelques minutes

Disclaimer : toute ressemblance avec le code d’un site existant permettant d’échanger des maisons est totalement fortuite.

Il est très facile d’abuser de MySQL pour le transformer en une arme de destruction massive pour les performances de votre hébergeur. Il y a deux manières très simples d’y arriver :

  • Transférer le plus souvent possible d’énormes quantités de données du serveur SQL au serveur web. Bien sûr, votre script n’a pas besoin de toutes ces données, mais utiliser un SELECT restreint au lieu d’un SELECT *, ou pire, utiliser LIMIT, c’est tellement compliqué … alors que c’est si simple à faire en PHP. Par exemple, dans de vieilles versions de SPIP, la fonction chargée de nettoyer le cache (stocké dans MySQL) transférait l’ensemble du cache avant de décider si chaque entrée devait être conservée ou non. (C’est corrigé depuis février 2005 d’après le CVS de SPIP)
  • Une autre manière plus pernicieuse est de générer dynamiquement des requêtes SQL sans se poser la question des cas extrèmes de requêtes générées. Prenons un petit exemple.

Créons une petite table :

CREATE TABLE `torture` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT , `value` INT, PRIMARY KEY ( `id` ) ) TYPE = MYISAM ;

À l’aide d’un petit script PHP, remplissons là avec 10000 valeurs, ce qui n’est pas si énorme que ça.

for ($i = 0; $i  

Maintenant, la partie intéressante : générons dynamiquement une requête.

 $sql = "SELECT id FROM torture WHERE TRUE"; for ($i = 0; $i  ".rand(0, 10000); }

Vous l'aurez compris, pour n = 10, ça donne qqchose comme SELECT id FROM torture WHERE TRUE AND value <> 14647 AND value <> 9936 AND value <> 10106 AND value <> 8136 AND value <> 5952 AND value <> 6908 AND value <> 14290 AND value <> 15359 AND value <> 2179 AND value <> 8005.

En se débrouillant pour passer le paramètre n à la page, on peut facilement tester pour différentes valeurs de n. J'ai testé sur le nouveau serveur MySQL d'Apinc, peu chargé et très performant. Si avec n = 10, la requête ne met que 0.03s à s'exécuter, elle met 2.8s avec n = 1000, et 22s (aye) avec n = 5000. Avec n = 10000, on atteint 38s.

Histoire d'être complet, on peut préciser qu'ajouter un index sur la colonne value ne change rien : on n'évite pas l'évaluation pour chaque ligne de la table. Par contre, en exprimant la même requête sous la forme SELECT id FROM torture WHERE value NOT IN(2791, 962, 49, 5845, 4425, 4129, 9905, 6468, 9681, 5776), on la transforme en une requête s'exécutant immédiatement (0.05s avec n = 10000)

Conclusions :

  • IN et NOT IN, c'est bien(tm).
  • Les requêtes générées dynamiquement sans connaitre leurs tailles, c'est mal(tm).