Хорошо, я получил отрицательный ответ по этому поводу, поэтому я решил проверить его:
CREATE TABLE userrole (
userid INT,
roleid INT,
PRIMARY KEY (userid, roleid)
);
CREATE INDEX ON userrole (roleid);
Запустите это:
<code><?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 "
\ п ";
запрос функции ($ str) {
mysql_query ($ строка);
if (mysql_error ()) {
echo "$ str:". mysql_error (). "\ П";
}
}
?>
Выход:
499872 users added.
Program ran for 56.5513510704 seconds.
Insert time 0.000113131663847 seconds.
Это добавляет 500 000 случайных комбинаций ролей пользователей и приблизительно 25 000, которые соответствуют выбранным критериям.
Первый запрос:
SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3
Время запроса: 0,312 с
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
Время запроса: 0,016 с
Это верно. Предлагаемая мною версия соединения * в 1022 раза быстрее, чем агрегатная версия.
Извините, но я делаю это для жизни и работы в реальном мире, а в реальном мире мы тестируем SQL, и результаты говорят сами за себя.
Причина этого должна быть довольно ясна. Совокупный запрос будет масштабироваться по стоимости в зависимости от размера таблицы. Каждая строка обрабатывается, агрегируется и фильтруется (или нет) с помощью предложения HAVING
. Версия объединения (с использованием индекса) выберет подмножество пользователей на основе заданной роли, затем проверит это подмножество для второй роли и, наконец, это подмножество для третьей роли. Каждый выбор (в терминах реляционной алгебры ) работает на все более малом подмножестве. Из этого можно сделать вывод:
Производительность версии соединения становится еще лучше с меньшим количеством совпадений.
Если было только 500 пользователей (из приведенного выше примера 500 тыс.), У которых были три указанные роли, версия соединения будет значительно быстрее. Совокупная версия не будет (и любое улучшение производительности является результатом переноса 500 пользователей вместо 25 КБ, что, очевидно, также получает и объединенная версия).
Мне также было любопытно посмотреть, как реальная база данных (например, Oracle) справится с этим. Поэтому я в основном повторил то же упражнение на Oracle XE (работающем на том же настольном компьютере с Windows XP, что и MySQL из предыдущего примера), и результаты практически идентичны.
Объединения кажутся неодобрительными, но, как я продемонстрировал, агрегированные запросы могут быть на порядок медленнее.
Обновление: После некоторого расширенного тестирования картина будет более сложной, и ответ будет зависеть от ваших данных, вашей базы данных и других факторов. Мораль этой истории - тест, тест, тест.