Ok, j'ai été déclassé à ce sujet, alors j'ai décidé de le tester :
CREATE TABLE userrole (
userid INT,
roleid INT,
PRIMARY KEY (userid, roleid)
);
CREATE INDEX ON userrole (roleid);
Exécutez ceci :
<?php
ini_set('max_execution_time', 120); // takes over a minute to insert 500k+ records
$start = microtime(true);
echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
echo "Connect error: " . mysql_error() . "\n";
}
mysql_select_db('scratch');
if (mysql_error()) {
echo "Selct DB error: " . mysql_error() . "\n";
}
$users = 200000;
$count = 0;
for ($i=1; $i<=$users; $i++) {
$roles = rand(1, 4);
$available = range(1, 5);
for ($j=0; $j<$roles; $j++) {
$extract = array_splice($available, rand(0, sizeof($available)-1), 1);
$id = $extract[0];
query("INSERT INTO userrole (userid, roleid) VALUES ($i, $id)");
$count++;
}
}
$stop = microtime(true);
$duration = $stop - $start;
$insert = $duration / $count;
echo "$count users added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $insert seconds.\n";
echo "</pre>\n";
function query($str) {
mysql_query($str);
if (mysql_error()) {
echo "$str: " . mysql_error() . "\n";
}
}
?>
\n";function query($str) { mysql_query($str); if (mysql_error()) { echo "$str:" . mysql_error() . "\n" ; }}?> Sortie :
499872 users added.
Program ran for 56.5513510704 seconds.
Insert time 0.000113131663847 seconds.
Cela ajoute 500 000 combinaisons utilisateur-rôle aléatoires et il y en a environ 25 000 qui correspondent aux critères choisis.
Première requête :
SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3
Temps de requête :0,312 s
SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid AND t2.roleid = 2
JOIN userrole t3 ON t2.userid = t3.userid AND t3.roleid = 3
AND t1.roleid = 1
Temps de requête :0,016 s
C'est exact. La version jointe que j'ai proposée est vingt fois plus rapide que la version agrégée.
Désolé, mais je fais ça pour gagner ma vie et je travaille dans le monde réel et dans le monde réel, nous testons SQL et les résultats parlent d'eux-mêmes.
La raison de cela devrait être assez claire. La requête agrégée sera mise à l'échelle en coût avec la taille de la table. Chaque ligne est traitée, agrégée et filtrée (ou non) via le HAVING
clause. La version de jointure sélectionnera (à l'aide d'un index) un sous-ensemble d'utilisateurs en fonction d'un rôle donné, puis vérifiera ce sous-ensemble par rapport au deuxième rôle et enfin ce sous-ensemble par rapport au troisième rôle. Chaque sélection
(en algèbre relationnelle
termes) fonctionne sur un sous-ensemble de plus en plus petit. De cela, vous pouvez conclure :
Les performances de la version de jointure sont encore meilleures avec une incidence de correspondances plus faible.
S'il n'y avait que 500 utilisateurs (sur l'échantillon de 500 000 ci-dessus) qui avaient les trois rôles indiqués, la version de jointure deviendra beaucoup plus rapide. La version agrégée ne le fera pas (et toute amélioration des performances est le résultat du transport de 500 utilisateurs au lieu de 25 000, ce que la version jointe obtient évidemment aussi).
J'étais également curieux de voir comment une vraie base de données (c'est-à-dire Oracle) traiterait cela. J'ai donc essentiellement répété le même exercice sur Oracle XE (exécuté sur le même ordinateur de bureau Windows XP que MySQL de l'exemple précédent) et les résultats sont presque identiques.
Les jointures semblent être mal vues, mais comme je l'ai démontré, les requêtes agrégées peuvent être d'un ordre de grandeur plus lentes.
Mise à jour : Après quelques tests approfondis , le tableau est plus compliqué et la réponse dépendra de vos données, de votre base de données et d'autres facteurs. La morale de l'histoire est test, test, test.