Лучше ли фильтровать набор результатов с помощью предложения WHERE или с использованием кода приложения?

ОК, вот простая абстракция проблемы:

2 переменных (male_users и female_users) для хранения 2 групп пользователей, то есть мужчин и женщин

  • 1 способ - использовать два запроса для их выбора:

select * from users where gender = 'male', а затем сохраните результат в male_users

select * from users where gender = 'female ', а затем сохраните результат в female_users

  1. Другой способ - запустить только один запрос:

'select * from users', а затем перейдем к набору результатов для фильтрации пользователей-мужчин в программе PHP-фрагмент кода будет выглядеть следующим образом:

$result = mysql_query('select * from users');

while (($row=mysql_fetch_assoc(result)) != null) {
  if ($row['gender'] == 'male'){// add to male_users}
  else if ($row['gender'] == 'female'){// add to female_users}
}

какой из них более эффективен и рассматривается как лучший подход?

это просто простая иллюстрация проблемы. реальный проект может иметь таблицы lager для запроса и дополнительные параметры фильтра.

заблаговременно!

Ответ 1

Эмпирическое правило для любого приложения - это позволить БД делать то, что он делает хорошо: фильтрация, сортировка и объединение.

Разделите запросы на свои собственные функции или методы класса:

$men = $foo->fetchMaleUsers();
$women = $foo->fetchFemaleUsers();

Update

Я принял демонстрацию Steven PostgreSQL полного запроса сканирования таблицы, выполняющего в два раза больше, чем два отдельных проиндексированных запроса, и имитировал его с использованием MySQL (который используется в фактическом вопросе):

Схема

CREATE TABLE `gender_test` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `gender` enum('male','female') NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26017396 DEFAULT CHARSET=utf8

Я изменил тип пола, чтобы не быть VARCHAR (20), поскольку он более реалистичен для целей этого столбца, я также предоставляю первичный ключ, как вы ожидали бы от таблицы вместо произвольного значения DOUBLE.

Неиндексированные результаты

mysql> select sql_no_cache * from gender_test WHERE gender = 'male';

12995993 rows in set (31.72 sec)

mysql> select sql_no_cache * from gender_test WHERE gender = 'female';

13004007 rows in set (31.52 sec)

mysql> select sql_no_cache * from gender_test;

26000000 rows in set (32.95 sec)

Я надеюсь, что это не нуждается в объяснении.

Индексированные результаты

ALTER TABLE gender_test ADD INDEX (gender);

...

mysql> select sql_no_cache * from gender_test WHERE gender = 'male';

12995993 rows in set (15.97 sec)

mysql> select sql_no_cache * from gender_test WHERE gender = 'female';

13004007 rows in set (15.65 sec)

mysql> select sql_no_cache * from gender_test;

26000000 rows in set (27.80 sec)

Показанные здесь результаты радикально отличаются от данных Стивена. Индексированные запросы выполняются почти в два раза быстрее, чем полное сканирование таблицы. Это из таблицы с правильной индексацией, используя определения столбцов здравого смысла. Я вообще не знаю PostgreSQL, но в примере Стивена должна быть некоторая значительная неправильная конфигурация, чтобы не показывать похожие результаты.

Учитывая репутацию PostgreSQL для того, чтобы делать что-то лучше, чем MySQL, или, по крайней мере, так хорошо, я полагаю, что PostgreSql продемонстрировал бы подобную производительность, если правильно ее использовать.

Также обратите внимание, что на этой же машине слишком упрощенный цикл, выполняющий 52 миллиона сравнений, выполняет дополнительные 7,3 секунды.

<?php
$N = 52000000;
for($i = 0; $i < $N; $i++) {
    if (true == true) {
    }
}

Я думаю, что довольно очевидно, что лучше подходит данным данным.

Ответ 2

Я бы сказал, что на самом деле нет причин заставить вашу БД выполнять дополнительную работу по оценке предложения WHERE. Учитывая, что вы действительно хотите все записи, вам придется выполнять работу по их извлечению. Если вы сделаете одиночный SELECT из таблицы, он будет извлекать их все в таблице-порядке, и вы можете разбить их самостоятельно. Если вы выбрали WHERE male и SELECT WHERE female, вам нужно нажать индекс для каждой операции, и вы потеряете некоторую локальность данных.

Например, если ваши записи на диске чередуются с мужчиной-женщиной, и у вас есть набор данных, намного больший, чем память, вам, вероятно, придется дважды считывать всю базу данных, если вы делаете два отдельных запроса, тогда как один SELECT для обоих будет односкатное сканирование.

EDIT: Поскольку я сбился с ума, я решил провести тест. Я создал таблицу

СОЗДАТЬ ВРЕМЕННЫЙ ТАБЛИЦ gender_test (some_data DOUBLE PRECISION, gender CHARACTER VARYING (20));

Я создал некоторые случайные данные,

выберите пол, счетчик (*) из группы gender_test по полу;
  пол | Количество
 -------- + ----------
  женщины | 12603133
  мужчина | 10465539
  (2 строки)

Сначала запустите эти тесты без индексов, и в этом случае я уверен, что я прав...

test = > EXPLAIN ANALYZE SELECT * FROM gender_test WHERE gender = 'male',
                                                       QUERY PLAN


Seq Scan on gender_test (стоимость = 0.00..468402.00 строк = 96519 ширина = 66) (фактическое время = 0.030..4595.367 rows = 10465539 loops = 1)
Фильтр: ((пол):: текст = 'мужчина':: текст)
Общая продолжительность выполнения: 5150,263 мс

test = > EXPLAIN ANALYZE SELECT * FROM gender_test WHERE gender = 'female',
                                                       QUERY PLAN


Seq Scan on gender_test (стоимость = 0,00..468402,00 строк = 96519 ширина = 66) (фактическое время = 0,029..4751,219 строк = 12603133 петли = 1) Фильтр: ((пол):: текст = 'женский':: текст)
Общая продолжительность выполнения: 5418.891 мс

test = > EXPLAIN ANALYZE SELECT * FROM gender_test;
                                                         QUERY PLAN


Seq Scan on gender_test (стоимость = 0,00..420142.40 строк = 19303840 ширина = 66) (фактическое время = 0,021..3326.164 строк = 23068672 циклов = 1)
Общее время выполнения: 4543.393 мс (2 строки)

Забавный, выглядит как выборка данных в сканировании таблицы без фильтра, действительно быстрее! На самом деле, более чем в два раза быстрее! (5150 + 5418 > 4543). Как я и предсказывал!:-p

Теперь сделаем индекс и посмотрим, изменит ли он результаты...

CREATE INDEX test_index ON gender_test (пол);

Теперь, чтобы повторить те же запросы...

test = > EXPLAIN ANALYZE SELECT FROM gender_test WHERE gender = 'male',
                                                               QUERY PLAN


Сканирование растровой карты на gender_test (cost = 2164.69..195922.27 rows = 115343 width = 66) (фактическое время = 2008.877..4388.348 строк = 10465539 циклов = 1)
Перепроверить Cond: ((пол):: текст = 'мужчина':: текст)
 - > Индекс растрового изображения Сканирование на test_index (стоимость = 0,00..2135.85 строк = 115343 ширина = 0) (фактическое время = 2006.047..2006.047 rows = 10465539 loops = 1)
         Index Cond: ((пол):: text = 'male':: text)
Общее время выполнения: 4941,64 мс

test = > EXPLAIN ANALYZE SELECT * FROM gender_test WHERE gender = 'female',
                                                               QUERY PLAN


Растровое сканирование кучи на gender_test (cost = 2164.69..195922.27 rows = 115343 width = 66) (фактическое время = 1915.385..4269.933 rows = 12603133 loops = 1)
Recheck Cond: ((пол):: text = 'female':: text)
- > Индекс растрового изображения Сканирование на test_index (стоимость = 0,00..2135.85 строк = 115343 ширина = 0) (фактическое время = 1912.587..1912.587 rows = 12603133 loops = 1)
         Index Cond: ((пол):: text = 'female':: text)
Общее время выполнения: 4931,555 мс (5 строк)

test = > EXPLAIN ANALYZE SELECT * FROM gender_test;
                                                         QUERY PLAN


Seq Scan on gender_test (cost = 0.00..457790.72 rows = 23068672 width = 66) (фактическое время = 0.021..3304.836 rows = 23068672 loops = 1)
Общая продолжительность выполнения: 4523.754 мс

Забавно... сканирование всей таблицы за один раз по-прежнему вдвое быстрее! (4941 + 4931 против 4523)

ПРИМЕЧАНИЕ Там всевозможные способы ненаучности. Я работаю с 16 ГБ оперативной памяти, поэтому весь набор данных вписывается в память. Postgres не настроен на использование почти так много, но кеш диска все еще помогает... Я бы предположил (но не может быть уверен, что на самом деле попытаюсь), что эффекты только ухудшатся, как только вы нажмете диск. Я попробовал только индексирование btree Postgres по умолчанию. Я предполагаю, что разбиение на PHP не требует времени - не верно, но, вероятно, довольно разумное приближение.

Все тесты выполняются на Mac Pro 8-way 2.66 Xeon 16GB RAID-0 7200 об/мин

Кроме того, этот набор данных составляет 26 миллионов строк, что, вероятно, немного больше, чем большинство людей заботятся о...

Очевидно, что необработанная скорость - это не единственное, что вам нужно. Во многих (большинстве?) Приложениях вам будет больше нужна логическая "правильность" для их получения отдельно. Но, когда дело доходит до вашего босса, говорящего "нам нужно, чтобы это ускорилось", это, по-видимому, даст вам 2x ускорение. OP явно задал вопрос об эффективности. Счастливы?

Ответ 3

Если у вас 1 миллион пользователей, вы предпочитаете (учитывая, что половина из них - мужчина, а половина женщины):

  • выбор 1 миллиона пользователей из БД?
  • или только выборка из 500 пользователей из базы данных?

Я полагаю, вы ответите, что предпочитаете получать только половину пользователей;-) И, в зависимости от условия, если он более сложный, он может быть даже меньше этого.


В принципе, выборка данных меньше:

  • менее сеть используется "ни для чего" (т.е. для получения данных, которые будут немедленно отброшены)
  • меньше используемой памяти, особенно на сервере PHP
  • потенциально меньше доступа к диску на сервере MySQL - так как для получения с диска меньше данных

В общих случаях мы стараемся избегать получения большего количества необходимых данных; т.е. мы размещаем фильтрацию на стороне базы данных.


Конечно, это означает, что вам нужно будет подумать об индексах, которые вы поместите в таблицы базы данных: они должны будут соответствовать потребностям запросов, которые вы будете делать.