Подзапросы

Wicked

Новичок
Подзапросы

Стал изучать сабж в свете возможности оптимизировать с помощью них запросы.
Оказалось, что они работают как-то не так, как я ожидал :)

Опишу на примере:

Есть таблица `ext_feeder_user_feed` подписок пользователей `feeder_id` на фиды `feed_id`. Всего ~9400 записей.
Есть таблица `ext_feeder_feed` фидов с PK `id`. Всего ~7400 фидов.
Пользователь X подписан на 346 фидов.

Сначала попытался выбрать детальную информацию о фидах с помощью такого запроса:
[sql]select `id`, `title` from `ext_feeder_feed` where `id` IN (
select `feed_id` from `ext_feeder_user_feed` where `feeder_id` = 70861
);[/sql]
И думал, что он сначала выполнит подзапрос (потому что он не зависит от внешних факторов), и будет использовать его результаты для выполнения внешнего запроса, грубо говоря, в таком виде:
[sql]select `id`, `title` from `ext_feeder_feed` where `id` IN (10, 49, 894, 1223, etc.)[/sql]
На практике же оказалось, что он действует наоборот: берет все фиды, и по очереди проверяет, не подписан ли юзер на какие-нибудь из них, т.е. делает фуллскан.
Код:
+----+--------------------+----------------------+----------------+-----  -+-----------+---------+------+------+--------------------------+
| id | select_type        | table                | type           | poss   | key       | key_len | ref  | rows | Extra                    |
+----+--------------------+----------------------+----------------+-----  -+-----------+---------+------+------+--------------------------+
|  1 | PRIMARY            | ext_feeder_feed      | index          | NULL   | state_idx | 4       | NULL | 7359 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | ext_feeder_user_feed | index_subquery | ext_   | feed_id   | 8       | func |    1 | Using where              |
+----+--------------------+----------------------+----------------+-----  -+-----------+---------+------+------+--------------------------+
В то же время, я могу сделать запрос с обычным inner join-ом, и получить те же результаты с куда более вкусным explain'ом:
[sql]select `ext_feeder_feed`.`id`
from `ext_feeder_feed`
inner join `ext_feeder_user_feed` on (`ext_feeder_feed`.`id` = `ext_feeder_user_feed`.feed_id)
where `ext_feeder_user_feed`.`feeder_id` = 70861;[/sql]
Код:
+----+-------------+----------------------+--------+-----  -+-----------+---------+---------  -+------+-------------+
| id | select_type | table                | type   | poss   | key       | key_len | ref        | rows | Extra       |
+----+-------------+----------------------+--------+-----  -+-----------+---------+---------  -+------+-------------+
|  1 | SIMPLE      | ext_feeder_user_feed | ref    | ext_   | feeder_id | 8       | const      |  345 | Using index |
|  1 | SIMPLE      | ext_feeder_feed      | eq_ref | PRIM   | PRIMARY   | 8       | kia_test   |    1 | Using index |
+----+-------------+----------------------+--------+-----  -+-----------+---------+---------  -+------+-------------+
Итого:
1) Что я делаю не так? :)
2) Как вообще можно использовать подзапросы для оптимизации?
 

Gas

может по одной?
Ну вообще-то "... IN (subquery)" известная проблема mysql.
http://dev.mysql.com/doc/refman/5.0/en/subquery-restrictions.html

Помнится читал что в 5.1 оптимизировали, но вот сейчас подтверждения найти не могу, может и путаю чего.

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

Wicked

Новичок
я так понял, что в этом случае помог бы т.н. uncorrelated query (независимый от внешних факторов)... Сейчас попробую проверить...

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

-~{}~ 05.02.08 16:44:

про мой случай:
Subquery optimization for IN is not as effective as for the = operator or for the IN(value_list) operator.

A typical case for poor IN subquery performance is when the subquery returns a small number of rows but the outer query returns a large number of rows to be compared to the subquery result.

The problem is that, for a statement that uses an IN subquery, the optimizer rewrites it as a correlated subquery. Consider the following statement that uses an uncorrelated subquery:

SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);

The optimizer rewrites the statement to a correlated subquery:

SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);

If the inner and outer queries return M and N rows, respectively, the execution time becomes on the order of O(M×N), rather than O(M+N) as it would be for an uncorrelated subquery.

An implication is that an IN subquery can be much slower than a query written using an IN(value_list) operator that lists the same values that the subquery would return.
(c) http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html

кто-нибудь знает, это можно как-нибудь пофиксить? :)
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Автор оригинала: Wicked
кто-нибудь знает, это можно как-нибудь пофиксить? :)
Перейти на более другую СУБД с более полноценным оптимизатором. :)
 

PILOT

Новичок
подзапросы использовать нельзя на хоть сколько нибудь разумных табличках а join c derived запросами работает правильно.
То есть когда ситуация обязывает (хотя странно) использовать подзапрос надо делать конструкцию вида select from t1 join (select blabla) as t2
 

Wicked

Новичок
PILOT
да, я уже разобрался - так получается uncorrelated query. Причем в mysql это записано в subquery restrictions, т.е. по сути решение бага за счет другой недоработки :)
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Мыши плакали, кололись, но продолжать жрать кактус.
 

Wicked

Новичок
Sad Spirit
Лучше порадуйся, что у меня теперь есть сторонник постгреса среди коллег. А я довольно открыт для адекватной (отличной от "муся говно, постгрес рулез") информации. И если глобальная выгода перевесит оверхед на переезд, то мы наверное-таки переедем.

И этот коллега мне сегодня показал, что там в аналогичном случае все ок. +1 в пользу постгреса.
 

nail

Новичок
Автор оригинала: Wicked
кстати, интересно было бы взглянуть, какая производительность будет у doctrine в этом случае.
Хехе, только вчера как раз в том месте копался в кишках :) Фиксил там случай композитного primary key.
mysql не поддерживает limit в подзапросах, поэтому в этом случае доктрина для mysql не делает подзапрос.
Она делает два запроса:
select id ... limit N
select ... where id in (...)

И нечего выпендриваться с подзапросами :)
Два обычных запроса - это то же самое что и один с подзапросом, и кешировать это даже эффективнее.

-~{}~ 06.02.08 21:13:

кстати
http://www.mysqlperformanceblog.com/2008/02/06/mysql-60-subquery-optimizations-are-published/
 

Сергей Тарасов

Профессор
Да, я тоже натыкался на эту тему с подзапросами. В результате всеё было переписано на менне внятные, но в 10-100-1000 раз более быстрые JOIN'ы. :)
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Автор оригинала: berkut
Sad Spirit тебе pg платят?)
Нет, глумлюсь я абсолютно бесплатно, ради лулзов. :)

Автор оригинала: Wicked
А я довольно открыт для адекватной (отличной от "муся говно, постгрес рулез") информации.
Ну в эту тему сложно было писать более развёрнутый ответ, ибо вариантов немного:
  • продолжать с аппетитом кушать кактус, заменив (якобы давно и успешно реализованные) подзапросы на join'ы / несколько запросов (вариант nail и Сергей Тарасов),
  • ждать новую версию кактуса с более мягкими иголками,
  • перестать кушать кактус.
Ну и ещё вариант для настоящих джедаев: исправить оптимизатор MySQL своими силами.

Просто уж больно забавно процесс поедания кактуса со стороны выглядит, обычно народ отмазывается, что MySQL --- это их выбор, ибо он стоит у хостера. Но я сомневаюсь, что многие в этой теме пользуются услугами shared хостинга. :)
 

Сергей Тарасов

Профессор
Автор оригинала: Sad Spirit
Нет, глумлюсь я абсолютно бесплатно, ради лулзов. :)

Просто уж больно забавно процесс поедания кактуса со стороны выглядит, обычно народ отмазывается, что MySQL --- это их выбор, ибо он стоит у хостера. Но я сомневаюсь, что многие в этой теме пользуются услугами shared хостинга. :)
Многим просто лень тащится за новым кактусом с более мягкими иголками :)
 

nail

Новичок
Вообще-то в этом случае в очередной раз подзапросы-то и не нужны.

Вот есть у юзера список фидов, который почти никогда не меняется.
Лежит себе в кеше и лежит.

Когда надо сделать выборку статей, пишем условие "where feed_id in (...подставляем закешированные значения...)".
Все выбирается за один запрос.

Но вот мы решили все сделать "по-правильному", с подзапросами.
"where feed_id (select id from feed...)"
И что тут происходит? ДВА селекта - сначала фиды юзерадергаем, потом статьи.

На самом деле так почти всегда и получается - выгоднее вынести подзапрос в отдельный запрос.
(может поэтому в mysql подзапросы такие незаоптимизированные, потому что их использование мало что дает?)
 
Сверху