разделение одного запроса на четыре, чтобы избежать массовых соединений?
Итак, у меня есть запрос, который выглядит так:
SELECT col1, col2, col3 ...
FROM action_6_members m
LEFT JOIN action_6_5pts f ON f.member_id = m.id
LEFT JOIN action_6_10pts t ON t.member_id = m.id
LEFT JOIN action_6_weekly w ON w.member_id = m.id
WHERE `draw_id` = '1' ORDER BY m.id DESC LIMIT 0, 20;
Теперь это массовое объединение (3,5 миллиона *40 тысяч * 20 тысяч)
Итак, моя идея состояла в том, чтобы:
Сделать SELECT * FROM action_6_members WHERE
draw_id= '1' ORDER BY id DESC LIMIT 0, 20;
Затем повторите это, используя php для построения
$in = "IN(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)"
;
Затем запуститеselect * from action_6_5pts where member_id in $in
select * from action_6_10pts where member_id in $in
select * from action_6_weekly where member_id in $in
Затем объедините их все вместе с помощью php,
Это означает, что, хотя я использую четыре разных запроса, я выбираю только 20 строк из каждый, вместо того, чтобы выполнять объединение для всех.
Замечу ли я значительный бонус к производительности?
Обновление
Итак, общее мнение таково: "НЕ ДЕЛАЙ ЭТОГО!"
Вот общий обзор приложения
Он получает код,
Код может быть либо 5pt, 10pt, либо еженедельным кодом,
Все три типа кода представлены в отдельных таблицах. в трех таблицах есть код и идентификатор участника
Идентификатор участника ссылается на идентификатор в таблица action_6_members.
Когда утверждается код, данные заполняются в таблице action_6_members.
Идентификатор этого участника затем заполняется в таблице для заявленного кода.
Приведенный выше запрос выбирает первые двадцать участников.
Итак, мой вопрос таков.
Что я могу сделать, чтобы улучшить это?
Поскольку в настоящее время все истекает до запросов завершено.
Участники Action_6_members
CREATE TABLE `action_6_members` (
`id` int(11) NOT NULL auto_increment,
`draw_id` int(11) NOT NULL,
`mobile` varchar(255) NOT NULL,
`fly_buys` varchar(255) NOT NULL,
`signup_date` datetime NOT NULL,
`club` int(11) NOT NULL default '0' COMMENT '1 = yes, 2 = no',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1337 DEFAULT CHARSET=latin1
Действие_6_ 5 и 10 пунктов
CREATE TABLE `action_6_5pts` (
`code` varchar(255) NOT NULL,
`member_id` int(11) NOT NULL,
PRIMARY KEY (`code`),
KEY `member_id` (`member_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
Действие_6_ Еженедельно
CREATE TABLE `action_6_weekly` (
`id` int(11) NOT NULL auto_increment,
`code` varchar(255) NOT NULL,
`member_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `member_id` (`member_id`)
) ENGINE=MyISAM AUTO_INCREMENT=3250001 DEFAULT CHARSET=latin1
Обновление 2: объясните запрос
id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE m ALL \N \N \N \N 1390 Using temporary; Using filesort 1 SIMPLE f ALL member_id \N \N \N 36000 1 SIMPLE t ALL member_id \N \N \N 18000 Using where 1 SIMPLE w ref member_id member_id 4 m.id 525820 Using where
Только что закончил с этим: Последние данные о загрузке из БД 7.26, 4.60, 2.45
1.0 - это нормальная максимальная нагрузка...Все, что выше, означает, что он должен был "взорваться" и вызвать дополнительные процессы для обработки. т. е. 7,26 означает, что нагрузка в 7 раз превышает максимальную нагрузку на лезвие сервер и был вынужден обратиться к другим, чтобы помочь
Так что в настоящее время этот запрос больше, чем монстр, он ест монстров в качестве закуски...
8 answers
Как правило, если ваш SQL-запрос может полностью смоделировать то, что вы хотите сделать, то, скорее всего, это будет быстрее, чем разбивать его на части, склеенные вместе на PHP (или любом другом языке), в определенных пределах.
Эти границы таковы:
- В этом случае в MySQL не должно быть странного патологического поведения, скрывающегося в MySQL.
- У вас должны быть разумные индексы по всем необходимым столбцам.
- Нет (или нет вероятного ) случая который вы можете разумно обнаружить/обработать только в PHP, в котором вы хотели бы прервать запрос на полпути.
- Ваш результирующий набор не патологически огромен (например, он помещается в память и не превышает размера
max_allowed_packet
вmy.cnf
).
Теперь это не касается того, является ли ваш SQL (или предлагаемая альтернативная реализация в PHP) оптимальной для того, что вы делаете, но это можно решить только с учетом дополнительной информации о том, что делает ваше приложение и конечная точка, в которой вы находитесь на самом деле пытаюсь дотянуться. Это может быть хорошо, а может и нет.
При беглом взгляде на ваше обновление структуры таблицы мне ничего не бросается в глаза как вероятная причина большой проблемы с производительностью, но:
- Не используйте MyISAM, если вы не установили, что вам это нужно. InnoDB - ваш друг, особенно если таблицы имеют приличный объем трафика записи. Замки с полным столом MyISAM действительно могут укусить вас. Наличие ВНЕШНИХ КЛЮЧЕЙ для ссылочной целостности также было бы милый.
-
action_6_weekly
имеетid
в качествеPRIMARY KEY
иUNIQUE KEY
вкл...id
. Это излишне.PRIMARY KEY
фактически является надмножествомUNIQUE KEY
, вам не нужно создавать отдельныйUNIQUE KEY
. - Вывод
EXPLAIN
по вашему запросу был бы интересным.
Многократные поездки туда и обратно между приложением и базой данных? Нет, это не обеспечит прирост производительности по сравнению с одним запросом.
Для этого вам не нужно использовать PHP, вы можете сделать это в одном запросе с подзапросами или в хранимой процедуре с несколькими запросами.
Чтобы узнать, какой из них быстрее, сравните их.
Как ни странно, я собираюсь не согласиться с консенсусом, ну, по крайней мере, частично.
Прежде всего, вы никогда не должны использовать ЛЕВОЕ СОЕДИНЕНИЕ. Это заманчиво, но это почти всегда плохая идея. В вашем случае я предполагаю, что таблицы action_6_5pts, action_6_10pts и action_weekly могут содержать не все идентификаторы участников. (Я предполагаю, что ваши данные, поэтому, если каждая таблица гарантированно содержит все идентификаторы участников, тогда уберите ЛЕВУЮ часть вашего запроса, и все будет в порядке.)
Я подозреваю, что есть вероятно, это лучший способ, которым вы могли бы выложить свои данные в первую очередь. Как правило, рекомендуется объединять одни и те же типы данных в одну таблицу. Я не хочу строить догадки о ваших данных, поэтому приведу вам псевдопример. Я много раз видел, как люди берут похожие данные и разделяют их на несколько таблиц (меньшие таблицы лучше, верно?). Не всегда. Например, если вы создавали систему счетов-фактур, может возникнуть соблазн подумать о разделении счетов-фактур по месяцам на отдельные столы. Таким образом, вы создаете invoice_Jan2010, invoice_Feb2010... и т.д. Но что, если вы захотите поискать? Один и тот же клиент, вероятно, работает не во все месяцы, поэтому трудно составить список только с этим клиентом, не используя ЛЕВОЕ СОЕДИНЕНИЕ. Фу. Нам не нравится ЛЕВОЕ ПРИСОЕДИНЕНИЕ! Это медленно!
Лучший способ приблизиться к этому - иметь единую таблицу счетов-фактур с датой (индексированной!) и идентификатором каждого клиента. Любые соединения гарантированно найдут счет-фактуру, если только для клиента его не существует (что не было бы материя)
Теперь в вашем случае, может быть, вы могли бы сделать флаги 5pts и 10pts в одной таблице, а еженедельная дата - это дата? Я делаю предположения, не зная больше, трудно дать вам "правильный" ответ.
Теперь я сказал, что не согласен с консенсусом. Если вы не изменяете свои данные, как правило, если у вас очень большая таблица, как вы говорите, разделение на 4 запроса с использованием операторов IN - лучшая идея, чем ОБЪЕДИНЕНИЕ СЛЕВА. Если вы хотите ускорить его, вы можете объединить все 4 в 1 с помощью UNION. Тот все равно должно быть быстрее, чем ЛЕВОЕ СОЕДИНЕНИЕ.
Вы также можете легко это доказать. Возьмите свой запрос, поставьте перед ним ключевое слово EXPLAIN и выполните его непосредственно в Mysql (используя один из инструментов: командную строку, графический интерфейс Mysql или даже phpmyadmin). Это даст вам представление о том, как он планирует объединить таблицы вместе.
Объяснение вывода слишком длинное для этого ответа, но в целом каждая строка вывода покажет вам, к скольким строкам присоединится запрос. Чем меньше, тем лучше. Оно будет также расскажу вам, как он собирается ПРИСОЕДИНИТЬСЯ. "Использование временного" или "Использование сортировки файлов" - это то, чего вы хотите избежать, если это возможно (хотя, если вы отсортируете, это появится, так что будьте готовы). Там также есть столбец для того, с помощью какого ключа будут соединены строки. Если этот столбец пуст, вам следует попытаться создать индекс, чтобы он работал лучше.
Надеюсь, это поможет! Удачи!
Не делай этого. база данных очень быстро объединяет таблицы и выбирает соответствующие строки - намного быстрее, как если бы вы выполняли много одиночных запросов.
Вы не будете знать, даст ли вам этот подход прирост производительности или насколько, пока не попробуете его. По моему опыту, изменение такого рода запросов на дискретные - это не то, что вы можете предсказать. То, что вы ищете, - это переломный момент в MySQL, когда создание внутренних таблиц, превышающих определенный размер, является убийственным. Как только вы узнаете, где находится эта точка в вашей установке , вы сможете играть в игры с объединением запросов и последующей обработкой.
Вы должны использовать предложение in с соединением, а не использовать Ограничение. Ограничение выполняется после соединения, а не является частью запроса.
Возможно, я схожу с ума, но я не вижу индекса в таблице action_6_members
для поля, которое вы фильтруете по draw_id
в вашем исходном запросе.
Это означает, что запрос должен будет просмотреть все данные в таблице action_6_members
, а затем присоединиться к остальным.
Добавление индекса в столбец draw_id
, вероятно, поможет здесь.
Вы могли бы создать комбинированный ключ (draw_id
,id
), но это, вероятно, не принесло бы вам большой пользы, если бы вы не извлекали какие-либо данные из таблицы action_6_members
(если это не так, то вместо чтения таблицы данных можно использовать многопольный индекс)
Надеюсь, это поможет...