Скорость и оптимизация запроса

Profic

just Profic (PHP5 BetaTeam)
Скорость и оптимизация запроса

Имеются таблицы:
>show create table t98;
CREATE TABLE `t98` (
`id` int(10) unsigned NOT NULL default '0',
`name_rus` varchar(255) NOT NULL default '',
`name_eng` varchar(255) NOT NULL default '',
`name_style` tinyint(1) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `name_rus` (`name_rus`),
KEY `name_eng` (`name_eng`)
) TYPE=MyISAM

>show create table t37;
CREATE TABLE `t37` (
`id` int(10) unsigned NOT NULL default '0',
`iname` int(10) unsigned NOT NULL default '0',
`tname` int(10) unsigned NOT NULL default '0',
...
`pharmgroupatccode` varchar(255) NOT NULL default '',
...
PRIMARY KEY (`id`),
KEY `iname` (`iname`),
KEY `tname` (`tname`),
...
KEY `pharmgroupatccode` (`pharmgroupatccode`),
...
) TYPE=MyISAM
В t98 - 17130 записей, в t37 - 75416 записей.

Нужно выбрать записи из t98 по условиям из t37. Сейчас это делается примерно таким запросом:
SELECT DISTINCT t98.name_rus AS item, t98.id, t98.name_style FROM t98 LEFT JOIN t37 ON t98.id = t37.tname WHERE t37.pharmgroupatccode LIKE '.%' AND t98.name_rus <> '' ORDER BY name_rus ASC LIMIT 0,150
Это - основа запроса, реально идет 3 запроса (для составления алфавитного указателя, для подсчета кол-ва подходящих записей (для постраничного отображения) и собственно для данных), но условия у всех одинаковые.
Так вот на такие запросы тратиться непозволительно много времени (на алфавитный указатель и подсчет кол-ва записей по ~2 секунды, на саму выборку данных ~4 секунды). Есть ли у кого-нить идеи, как этот запрос можно изменить для уменьшения его времени выполнения? Мне ничего в голову не приходит :(

Для справки:
1) Вывод EXPLAIN для вышеуказанного запроса:
Код:
+-------+------+---------------+-------+---------+--------+-------+---------------------------------------------+
| table | type | possible_keys | key   | key_len | ref    | rows  | Extra                                       |
+-------+------+---------------+-------+---------+--------+-------+---------------------------------------------+
| t98   | ALL  | NULL          | NULL  |    NULL | NULL   | 17130 | where used; Using temporary; Using filesort |
| t37   | ref  | tname         | tname |       4 | t98.id |     4 | where used; Distinct                        |
+-------+------+---------------+-------+---------+--------+-------+---------------------------------------------+
Мне он совсем не нравиться, но что можно сделать ума не приложу, я уж и с индексами наэксперементировался, а толку ноль :(
2) Платформа - Win32 (WinXP)
MySQL/3.23.49; PHP/4.3.0; Apache/2.0.44 (хотя последние два пункта работают нормально и от них нифига не зависит)
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Re: Скорость и оптимизация запроса

Автор оригинала: Profic
...WHERE t37.pharmgroupatccode LIKE '.%'...
У меня нет опыта работы с Мысклем, но что-то подсказывает: он не умеет использовать индексы для таких выборок => full scan.
Сделай отдельное поле с первым символом строки 't37.pharmgroupatccode' и ищи по
Код:
t37.pgpc_first_symbol = '.'
Задание для самостоятельного изучения: как в этом решении пригодились бы триггеры или функциональные индексы.
 

Profic

just Profic (PHP5 BetaTeam)
Sad Spirit: отдельным полем здесь не отделаешься :(
В этом поле храниться ссылка на соответствующее поле в классификаторе (дереве) в виде .[node_num](.node_num)..., т.е. имеется строка нарастающей длины по глубине вложения...
Оно (значение для поиска) выбирается в отдельном запросе, т.е. в запросе вполне может быть что-то вроде
WHERE t37.pharmgroupatccode LIKE '.07.11.06%'
Этот запрос используется для выборки значений попадающих в категорию и всех ее подкатегорий, но даже запрос вида
WHERE t37.pharmgroupatccode = '.'
и подобные все-равно работает медленно :(

Блин, и базу не переделаешь, она такая у заказчика....
 

Profic

just Profic (PHP5 BetaTeam)
Sad Spirit: А случаем это не будет работать быстрее если переписать все с использованием PostgreSQL?
Я сам с ним не знаком, но если он будет работать быстрее
и с использованием его функцинальности будет проще реализовать, то я, пожалуй, даже попытаюсь уломать заказщика перепрыгнуть на PostgreSQL :)
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Автор оригинала: Profic
Sad Spirit: А случаем это не будет работать быстрее если переписать все с использованием PostgreSQL?
Если сохранить такую же схему --- очень в этом сомневаюсь... ;
Postgres умеет использовать индексы при поиске LIKE 'stuff%', но только если не используются локали => будут проблемы с русским языком.

Если pharmgroupatccode используется для фактического хранения древовидной структуры, то его и надо реализовать как дерево. На эту тему тут много чего писали, лично я пользуюсь моделью вложенных множеств. И тут Postgres поудобнее будет, т.к. можно реализовать добавление и удаление элементов через хранимые процедуры, да и запросы на выборку при наличии подзапросов и представлений (view) получаются гораздо более удобочитаемыми...

Резюме: переходи на Postgres или не переходи, а схему менять всё одно придётся.
 

ONK

Пассивист PHPСluba
Re: Re: Скорость и оптимизация запроса

Автор оригинала: Sad Spirit
У меня нет опыта работы с Мысклем, но что-то подсказывает: он не умеет использовать индексы для таких выборок => full scan.
Сделай отдельное поле с первым символом строки 't37.pharmgroupatccode' и ищи по
Код:
t37.pgpc_first_symbol = '.'
Задание для самостоятельного изучения: как в этом решении пригодились бы триггеры или функциональные индексы.
Мускул умеет нормально работать с индексами типа varchar, и like 'xx%' будет искать по индексу.


Тут проблемма в другом, в запросе сказано весь результат поиска отсортировать по полю name_rus . А для того чтобы отсортировать результат и выбрать из него 150 самых подходящих строк, надо перелопатить все строки соответсвующие условию выборки (17130). И PostgreSQL тут ничем не поможет (а может и хуже будет). Единственный вариант сильно повысить скорость выборки результата, - отказаться от сортировки..
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Re: Re: Re: Скорость и оптимизация запроса

Автор оригинала: ONK
Мускул умеет нормально работать с индексами типа varchar, и like 'xx%' будет искать по индексу.
Я конечно смотрю на мысклёвый EXPLAIN, аки баран на новые ворота, но что-то ни слова про возможность использования индекса по pharmgroupatccode не вижу... Profic, можешь для интереса попробовать выбор из одной таблицы по LIKE 'stuff%'?

Тут проблемма в другом, в запросе сказано весь результат поиска отсортировать по полю name_rus . А для того чтобы отсортировать результат и выбрать из него 150 самых подходящих строк, надо перелопатить все строки соответсвующие условию выборки (17130).
Хотя да... тут t98 в LEFT JOIN, а условие отбора только t98.name_rus <> '', так что похоже full scan по t98 гарантирован.

И PostgreSQL тут ничем не поможет (а может и хуже будет). Единственный вариант сильно повысить скорость выборки результата, - отказаться от сортировки..
Я бы сказал --- выкинуть ещё LEFT JOIN с DISTINCT'ом. Ибо пока DISTINCT не сделаешь, LIMIT не возьмёшь. :)

Да, я правильно понимаю план, он сначала делает DISTINCT по t37, а только потом начинает объединять с t98?

Profic, колько записей получается в рез-те запроса, если не использовать LIMIT?
 

tony2001

TeaM PHPClub
>Profic, можешь для интереса попробовать выбор из одной таблицы по LIKE 'stuff%'?
могу сказать заранее: индексы будут использоваться.

MySQL also uses indexes for LIKE comparisons if the argument to LIKE is a constant string that doesn't start with a wildcard character.
 

ONK

Пассивист PHPСluba
Re: Re: Re: Re: Скорость и оптимизация запроса

Автор оригинала: Sad Spirit


Я бы сказал --- выкинуть ещё LEFT JOIN с DISTINCT'ом. Ибо пока DISTINCT не сделаешь, LIMIT не возьмёшь. :)
DISTINCT с LIMIT в мускуле тоже работает нормально (после нахождения 150 подходящих и не повторяющихся записей сканирование прекратится).

Под условие выборки судя по explain подпадает всё что содержится в t98 (всё где поле name_rus не пустое ), но убивает этот запрос именно сортировка, т.к. без неё мускул прекратит сканирование после первых соответствующих условию 150 записей .

Тут есть ещё одна проблемма LIKE '.%' даёт больше чем 30% совпадений, из-за этого мускул переходит в полное сканировние таблици, но это всёравно не страшно, если убрать сортировку..
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Резюме ясно: вообще Мыскль самый быстрый, а Profic сам дурак, что такие запросы пишет. :D

Разговоры про LIMIT без ORDER --- разговоры в пользу бедных: не вполне понятно 150 каких именно записей он выдаст и не выдаст ли в разных порциях одни и те же записи, к тому же если делать LIMIT N,150 при N!=0, то время выполнения увеличится.
 

Profic

just Profic (PHP5 BetaTeam)
Sad Spirit:
Если оставить запрос как он есть, но убрать LIMIT то в результате получается 46703 записей
Всем :):
Спасибо, за то, что натолкнули на идею поиграться с выкидыванием разных частей запроса :)
Вот основной запрос - я его для удобства скопирую сюда:
SELECT DISTINCT t98.name_rus AS item, t98.id, t98.name_style FROM t98 LEFT JOIN t37 ON t98.id = t37.tname WHERE t37.pharmgroupatccode LIKE '.%' AND t98.name_rus <> '' ORDER BY name_rus ASC LIMIT 0,150
Время выполнения вычислялось 5-кратным запуском данного запроса в mysql.exe и высчитыванием среднего значения.
  • Итак, исходный запрос - 3.0 секунды
  • Запрос без ORDER BY t98.name_rus ASC - 4.7 секунды
  • Запрос без AND t98.name_rus <> '' - 3.2 секунды
  • Запрос без 2 предыдущих - 5.0 секунд
  • Запрос с FROM t98, t37 WHERE t98.id = t37.tname вместо FROM t98 LEFT JOIN t37 ON t98.id = t37.tname - 3.0 секунды
    С хвостом запроса мне эксперементировать надоело, начал эксперименты с началом
  • Запрос без DISTINCT - 0,3 секунды!!!
При этом эксплейн на такой запрос выдает:
Код:
+-------+-------+---------------+----------+---------+--------+-------+------------+
| table | type  | possible_keys | key      | key_len | ref    | rows  | Extra      |
+-------+-------+---------------+----------+---------+--------+-------+------------+
| t98   | index | NULL          | name_rus |     255 | NULL   | 17130 | where used |
| t37   | ref   | tname         | tname    |       4 | t98.id |     4 | where used |
+-------+-------+---------------+----------+---------+--------+-------+------------+
Что заметно лучше, чем все предыдущее...
Но, в этом случае выводятся дубликаты, а как переписать запрос без использования DISTINCT, но чтобы дубликаты не выводились? (Я, к сожалению, не силен в разных типах JOIN-ов...)
Останется только сделать спаренный ключ по tname и pharmgroupatccode, чтобы все было просто зашибись :)
 

Profic

just Profic (PHP5 BetaTeam)
Хм, нифига не понимаю, перепробовал разные типы JOIN-ов из мусклевого мануала, а толку - ноль, все равно возвращаются дубликаты, ели не использовать DISTINCT...

Неужели нет такого типа JOIN-а, чтобы из первой таблицы выбиралось только одно значение...
 

Апельсин

Оранжевое создание
> Неужели нет такого типа JOIN-а, чтобы из первой таблицы выбиралось только одно значение...

JOIN не для этого предназначен.
 
Сверху