Почему MySQL не использует индекс при JOIN?

FB3

Новичок
Почему MySQL не использует индекс при JOIN?

Имеются два таблицы:
[sql]CREATE TABLE `stat_installations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`player_id` int(10) NOT NULL,
`source` int(10) NOT NULL DEFAULT '0',
`referrer` int(10) unsigned NOT NULL DEFAULT '0',
`date` datetime NOT NULL,
`date_index` date DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `date_index` (`date_index`),
KEY `player_id` (`player_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `stat_preloader_visits` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`player_id` int(10) unsigned NOT NULL,
`owner_id` int(10) unsigned NOT NULL,
`installed` tinyint(3) unsigned NOT NULL DEFAULT '0',
`from_wall` tinyint(3) unsigned NOT NULL DEFAULT '0',
`referrer` int(10) unsigned NOT NULL DEFAULT '0',
`date` datetime NOT NULL,
`date_index` date DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `date` (`date`),
KEY `date_index` (`date_index`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;[/sql]

Запрос без JOIN использует индекс:
[sql]EXPLAIN
SELECT
COUNT(*) AS 'cnt',
spv.`date_index` 'date'
FROM `stat_preloader_visits` spv
WHERE spv.`date_index` BETWEEN '2010-07-12' AND '2010-08-12'
GROUP BY spv.`date_index`;[/sql]
Код:
+----+-------------+-------+-------+---------------+------------+---------+------+---------+--------------------------+
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows    | Extra                    |
+----+-------------+-------+-------+---------------+------------+---------+------+---------+--------------------------+
|  1 | SIMPLE      | spv   | index | date_index    | date_index | 4       | NULL | 4844330 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+---------+--------------------------+
Запрос с присоединением другой таблицы не использует индекс в начальной таблице:
[sql]EXPLAIN
SELECT
COUNT(*) AS 'cnt',
spv.`date_index` 'date'
FROM `stat_preloader_visits` spv,
`stat_installations` si
WHERE spv.`date_index` BETWEEN '2010-07-12'
AND '2010-08-12'
AND spv.`player_id` = si.`player_id`
GROUP BY spv.`date_index`;[/sql]
Код:
+----+-------------+-------+------+---------------+-----------+---------+------------------------------+---------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key       | key_len | ref                          | rows    | Extra                                        |
+----+-------------+-------+------+---------------+-----------+---------+------------------------------+---------+----------------------------------------------+
|  1 | SIMPLE      | spv   | ALL  | date_index    | NULL      | NULL    | NULL                         | 4844330 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | si    | ref  | player_id     | player_id | 4       | club_stat_test.spv.player_id |       1 | Using where; Using index                     |
+----+-------------+-------+------+---------------+-----------+---------+------------------------------+---------+----------------------------------------------+
Собственно, не могу понять почему...
 

Gas

может по одной?
попробуй сделать optimize table stat_preloader_visits; и потом запусти explain'ы.
Сколько всего записей в таблице stat_preloader_visits и сколько подпадает под between условие?
 

FB3

Новичок
После OPTIMIZE TABLE ничего не изменилось.
Сколько всего записей, видно в EXPLAIN, сейчас они все попадают в BETWEEN.
 

Gas

может по одной?
сейчас они все попадают в BETWEEN
тогда объясняю, в запросе без джойна идёт работа только с проиндексированным полем, а это значит что есть большая вероятность нахождения данных в key_buffer'е и тогда это(использование индекса) выгодно.

В запросе с джойном для каждой записи нужно ещё получить значение поля player_id, mysql одновременно не умеет работать с 2-мя независимыми индексами, по-этому нужно эти значения получить c "диска" (они конечно могут быть закешированы на уровне OS, но mysql точно этого не знает). В итоге выгодней прочитать _последовательно_ всю таблицу, чем делать _миллионы random seek'ов_ по диску.
 

FB3

Новичок
Примерно понятно...
Догадываюсь, что в текущей реализации соптимизировать запрос не получится.
 

Gas

может по одной?
можно добавить составной индекс (date_index, player_id)

из триксов, периодически выполнять
LOAD INDEX INTO CACHE `table`;

key_buffer_size хоть достаточно большой, а то записей только в этой таблице уже не мало?
 

FB3

Новичок
Gas
Не знаю, какой там сейчас key_buffer_size, нету доступа к серваку, на котором крутится, можно узнать попозже.
Смысла составного индекс не понял, у меня же player_id не используется ни в выборке, ни в группировке.
Сама по себе страничка, откуда выполняется запрос, совершенно не нагруженная (просмотр статистики несколько раз в день), так что пока лишь бы работало и данные верные отдавало.
 

Gas

может по одной?
Смысла составного индекс не понял
он используется в джойне, mysql то должен знать какое значение у поля player_id, без индекса он с диска будет читать, а так значение уже есть в индексе.

Раз это более-менее работает и редко нужно, то лучше не трогать )
 

FB3

Новичок
Автор оригинала: Gas
он используется в джойне, mysql то должен знать какое значение у поля player_id, без индекса он с диска будет читать, а так значение уже есть в индексе.

Раз это более-менее работает и редко нужно, то лучше не трогать )
Так если он все равно читает все записи, какая разница? Или я что-то не так понимаю?
На тот момент, когда я постил, оно не работало правильно, хотя какие-то данные выбирались :) Я думаю, что оно помрет и совсем перестанет работать через месяц-другой :)
 

Gas

может по одной?
Так если он все равно читает все записи, какая разница? Или я что-то не так понимаю?
ещё раз, если есть только индекс по date_index, то он может быть использован (теоритически взят частично или весь из памяти - key_buffer'а для myisam) для where и group by. Но так-как в запросе джойн, то кроме значения date_index, для каждой строки нужно получить значение поля player_id (иначе как приджойнить другую таблицу), а за ним нужно лезть на диск. Так-как вегребается большая часть таблицы, то быстрее её всю сразу считать, чем по ведёрку туда-сюда тягать - считывать запись в случайном порядке, так как сортировка записей в индексе и физическое их расположение на диске, ествественно, различно. НО! Если будет составной индекс (date_index, player_id), то mysql'ю не нужно лезть на диск за значением player_id, оно уже лежит в индексе рядом со значением date_index.

Пример: для своего запроса добавь LIMIT 10, одиночный индекс по date_index сразу должен заработать, так-как в where и group by он используется и при этом нужно получить всего 10 записей, а не все, то не страшно дёрнуть рандомно диск для получения player_id. А когда таких дёрганий слишком много, то уже лучше всё считать последовательно и не мучиться со случайными обращениями. Не зря во всех базах есть такая вешь как оптимизатор, которая выбирает план выполнения запроса.

ещё но ), у тебя это статистика, то скорее всего количество insert'ов в разы превышает количество select'ов, значит не добавляй лишний индексов без надобности, раз у тебя репорт дёргается несколько раз в день и не сильно тормозит, то пока ничего и не делай )
 

Wicked

Новичок
если в таблицу stat_installations писать ту же самую дату, что пишется в stat_preloader_visits, то джоин можно убрать

остальное не читал :)
 
Сверху