Правильная индексация / оптимизация MySQL GROUP BY и JOIN Query - PullRequest
3 голосов
/ 23 августа 2011

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

См. Правку № 2 ниже для лучшего примера


[Это был оригинальный вопрос, но он не очень хорошо отражает то, что я спрашиваю.]

Скажем, у меня есть 2 таблицы, каждая с 4 столбцами:

  • ключ (int, автоинкремент)
  • c1 (дата)
  • c2 (varchar длины 3)
  • c3 (также varchar длины3)

И я хочу выполнить следующий запрос:

SELECT t.c1, t.c2, COUNT(*)
FROM test1 t
LEFT JOIN test2 t2 ON t2.key = t.key
GROUP BY t.c1, t.c2

Оба поля key проиндексированы как первичные ключи.Я хочу получить количество строк, возвращаемых в каждой группе c1, c2.

Когда я объясняю этот запрос, я получаю "использование временного; использование сортировки файлов".Фактическая таблица, для которой я выполняю этот запрос, содержит более 500 000 строк, так что это означает, что этот запрос занимает много времени.

Итак, мой вопрос (при условии, что я не делал ничего плохого в запросе): есть лиспособ индексировать эту таблицу, чтобы исключить временное использование / сортировку файлов?

Заранее спасибо за любую помощь.

Редактировать

Вот таблицаопределение (в этом примере обе таблицы идентичны - на самом деле это не так, но я не уверен, что это имеет значение на данном этапе):

CREATE TABLE `test1` (
 `key` int(11) NOT NULL auto_increment,
 `c1` date NOT NULL,
 `c2` varchar(3) NOT NULL,
 `c3` varchar(3) NOT NULL,
 PRIMARY KEY  (`key`),
 UNIQUE KEY `c1` (`c1`,`c2`),
 UNIQUE KEY `c2_2` (`c2`,`c1`),
 KEY `c2` (`c2`,`c3`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

Полный оператор EXPLAIN:

id   select_type  table  type    possible_keys  key      key_len  ref             rows   Extra
1    SIMPLE       t      ALL     NULL           NULL     NULL     NULL            2      Using temporary; Using filesort
1    SIMPLE       t2     eq_ref  PRIMARY        PRIMARY  4        tracking.t.key  1      Using index

Это только для моих примеров таблиц.В моих реальных таблицах строки для t говорят 500 000+ (каждая строка в таблице, хотя это может быть связано с чем-то другим).


Edit # 2

Вот более конкретный пример, чтобы лучше объяснить мою ситуацию.

Допустим, у меня есть данные о бейсбольных матчах Малой лиги.У меня есть две таблицы.Один хранит данные об играх:

CREATE TABLE `ex_games` (
 `game_id` int(11) NOT NULL auto_increment,
 `home_team` int(11) NOT NULL,
 `date` date NOT NULL,
 PRIMARY KEY  (`game_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

Другой хранит данные о летучих мышах в каждой игре:

CREATE TABLE `ex_atbats` (
 `ab_id` int(11) NOT NULL auto_increment,
 `game` int(11) NOT NULL,
 `team` int(11) NOT NULL,
 `player` int(11) NOT NULL,
 `result` tinyint(1) NOT NULL,
 PRIMARY KEY  (`hit_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

Итак, у меня два вопроса.Давайте начнем с простой версии: я хочу вернуть список игр с подсчетом количества бит в каждой игре.Поэтому я думаю, что сделал бы что-то вроде этого:

SELECT date, home_team, COUNT(h.ab_id) FROM `ex_atbats` h
LEFT JOIN ex_games g ON g.game_id = h.game
GROUP BY g.game_id

Этот запрос использует файловую сортировку / временный.Есть ли лучший способ структурировать это или индексировать таблицы, чтобы избавиться от этого?

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

SELECT g.date, g.home_team, COUNT(ab.ab_id), COUNT(ab2.ab_id) FROM `ex_atbats` ab
LEFT JOIN ex_games g ON g.game_id = ab.game
LEFT JOIN ex_atbats ab2 ON ab2.ab_id = ab.ab_id - 1 AND ab2.result = ab.result
GROUP BY g.game_id

Это правильный способ структурировать этот запрос?При этом также используется сортировка файлов / временная.

Так каков оптимальный способ выполнения этих задач?

Еще раз спасибо.

Ответы [ 4 ]

1 голос
/ 23 августа 2011

Фразы Using temporary/filesort обычно не связаны с индексами, используемыми в операции JOIN.Существует множество примеров, когда вы можете установить все индексы (они отображаются в столбцах key и key_len в EXPLAIN), но вы все равно получаете Using temporary и Using filesort.

Узнайте, что говорится в руководстве о Using temporary и Using filesort:

Наличие объединенного индекса для всех столбцов, используемых в предложении GROUP BY, может помочь при определенных обстоятельствах избавиться от Using filesort.Если вы также введете ORDER BY, вам может потребоваться добавить более сложные индексы.

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

1 голос
/ 23 августа 2011

Прежде всего, определения таблиц имеют значение. Одно дело объединить с использованием двух первичных ключей, другое объединить с помощью первичного ключа с одной стороны и неуникального ключа с другой, и т. Д. Также важно, какой тип механизма используют таблицы, поскольку InnoDB обрабатывает первичные ключи иначе, чем MyISAM. двигатель.


Что я заметил, так это то, что в таблице test1 комбинация (c1,c2) является уникальной и поля не могут быть обнуляемыми. Это позволяет переписать ваш запрос как:

SELECT t.c1, t.c2, COUNT(*)
FROM test1 t
LEFT JOIN test2 t2 ON t2.key = t.key
GROUP BY t.key

Это даст одинаковые результаты при использовании одного и того же поля для JOIN и GROUP BY. Обратите внимание, что MySQL позволяет использовать в полях списка SELECT, которых нет в списке GROUP BY, без использования агрегатных функций. Это не разрешено в большинстве других систем и некоторые считают ошибкой. В этой ситуации, хотя это очень приятная особенность. Каждая строка может быть идентифицирована как (key) или (c1,c2), поэтому не должно иметь значения, какой из этих двух элементов используется для группировки.


Еще одна вещь, на которую следует обратить внимание: когда вы используете LEFT JOIN, для подсчета обычно используется объединяющий столбец с правой стороны: COUNT(t2.key), а не COUNT(*). Ваш исходный запрос выдаст 1 в этом столбце для записей в test1, которые не соответствуют ни одной записи в test2, поскольку он подсчитывает строки, в то время как вы, вероятно, хотите подсчитать связанные записи в test2 - и показывает 0 в тех случаях.

Итак, попробуйте этот запрос и опубликуйте объяснение:

SELECT t.c1, t.c2, COUNT(t2.key)
FROM test1 t
LEFT JOIN test2 t2 ON t2.key = t.key
GROUP BY t.key
0 голосов
/ 22 февраля 2014

Для innodb это будет работать, так как индекс по умолчанию содержит ваш первичный ключ. Для myisam у вас должен быть ключ, поскольку последний столбец вашего индекса будет «ключом». Это даст оптимизаторам все ключи в одном и том же порядке, и он может пропустить сортировку. Вы не можете выполнять какие-либо запросы диапазона по префиксу индекса theN, и вы сразу возвращаетесь в файловую сортировку. в настоящее время борется с похожей проблемой

0 голосов
/ 23 августа 2011

Индексы помогают с объединением, но вам все равно нужно выполнить полную сортировку, чтобы выполнить группировку.По сути, он все еще должен обрабатывать каждую запись в наборе.

Добавление условия where и ограничение набора будет выполняться быстрее, конечно.Это просто не даст вам желаемых результатов.

Могут быть и другие варианты, кроме группировки по всей таблице.Я заметил, что вы делаете SELECT * - что вы пытаетесь получить из запроса?

SELECT DISTINCT c1, c2 ОТ теста t ВЛЕВО ПРИСОЕДИНЯЕТСЯ test2 t2 ON t2.key = t.key

Например,

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

РЕДАКТИРОВАТЬ - При чтении (http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html), Я узнал, что при правильных обстоятельствах индексы могут значительно помочь с группой:

Я вижу, что это должен быть отсортированный индекс (например, BTREE), а не HASH. Возможно:

CREATE INDEX c1c2 IN t (c1, c2) USING BTREE;

может помочь.

...