2009-08-04 4 views
7

У нас есть две таблицы, напоминающая простые теги записи структуры следующей образом (на самом деле это гораздо более сложные, но это суть проблемы):MySQL не использует индекс с JOIN, WHERE и ORDER

tag (A.a) | recordId (A.b) 
1   | 1 
2   | 1 
2   | 2 
3   | 2 
.... 

и

recordId (B.b) | recordData (B.c) 
1    | 123 
2    | 666 
3    | 1246 

Проблема заключается в получении упорядоченных записей с определенным тегом. Очевидный способ сделать это с простой присоединиться и индексы на (ПК) (Aa, Ab), (Ab), (PK) (Bb), (Bb, Bc), как например:

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c; 

Однако это дает неприятный результат FileSort:

+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          | 
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+ 
| 1 | SIMPLE  | A  | ref | PRIMARY,b  | PRIMARY | 4  | const  | 94 | Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | B  | ref | PRIMARY,b  | b  | 4  | booli.A.b | 1 | Using index         | 
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+ 

Используя огромное и чрезвычайно избыточное «материализованное представление» мы можем получить довольно приличную производительность, но это за счет усложнения бизнес-логики, а то, что мы хотели бы избегайте, тем более, что таблицы A и B уже являются MV: s (и необходимы для других запросов, а также для тех же запросов, что и UNION).

create temporary table C engine=innodb as (select A.a, A.b, B.c from A join B on A.b = B.b); 
explain select a, b, c from C where a = 44 order by c; 

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

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 AND B.c > 678 order by c; 

Но мы уверены, что сможем справиться с этим, если проблема с файловым контейнером исчезнет.

Кто-нибудь знает, почему простое объединение в codeblock 3 выше не будет использовать индекс для сортировки, и если мы каким-то образом обойдем проблему без создания нового MV?

Ниже приведен полный список SQL, который мы используем для тестирования.

DROP TABLE IF EXISTS A; 
DROP TABLE IF EXISTS B; 
DROP TABLE IF EXISTS C; 
CREATE TEMPORARY TABLE A (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a, b), INDEX idx_A_b (b)) ENGINE=INNODB; 
CREATE TEMPORARY TABLE B (b INT NOT NULL, c INT NOT NULL, d VARCHAR(5000) NOT NULL DEFAULT '', PRIMARY KEY(b), INDEX idx_B_c (c), INDEX idx_B_b (b, c)) ENGINE=INNODB; 

DELIMITER $$ 
CREATE PROCEDURE prc_filler(cnt INT) 
BEGIN 
     DECLARE _cnt INT; 
     SET _cnt = 1; 
     WHILE _cnt <= cnt DO 
       INSERT IGNORE INTO A SELECT RAND()*100, RAND()*10000; 
       INSERT IGNORE INTO B SELECT RAND()*10000, RAND()*1000, ''; 
       SET _cnt = _cnt + 1; 
     END WHILE; 
END 
$$ 
DELIMITER ; 

START TRANSACTION; 
CALL prc_filler(100000); 
COMMIT; 
DROP PROCEDURE prc_filler; 

CREATE TEMPORARY TABLE C ENGINE=INNODB AS (SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b); 
ALTER TABLE C ADD (PRIMARY KEY(a, b), INDEX idx_C_a_c (a, c)); 

EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44; 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE 1 ORDER BY B.c; 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b where A.a = 44 ORDER BY B.c; 
EXPLAIN EXTENDED SELECT a, b, c FROM C WHERE a = 44 ORDER BY c; 
-- Added after Quassnois comments 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c; 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10; 
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10; 
+0

Файлы-файлы встречаются в предложении ORDER BY. Как индексируется 'B.c'? – jason

+0

@jason: Я обновил SQL в сообщении, чтобы быть более читаемым. Теперь индексирование должно быть ясным. – Paso

ответ

9

Когда я пытаюсь воспроизвести этот запрос с использованием скриптов:

SELECT A.a, A.b, B.c 
FROM A 
JOIN B 
ON  A.b = B.b 
WHERE a = 44 
ORDER BY 
     c 

, он завершает в 0.0043 seconds (мгновенно), возвращает 930 строки и дает этот план:

1, 'SIMPLE', 'A', 'ref', 'PRIMARY', 'PRIMARY', '4', 'const', 1610, 'Using index; Using temporary; Using filesort' 
1, 'SIMPLE', 'B', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.A.b', 1, '' 

Пришло достаточно эффективен для такого запроса.

Для такого запроса вы не можете использовать один индекс как для фильтрации, так и для сортировки.

Смотрите эту статью в своем блоге на более подробные объяснения:

Если вы ожидаете, что ваш запрос, чтобы вернуть несколько записей, вы должны использовать индекс на A для фильтрации, а затем сортировать с помощью filesort (как и в предыдущем запросе).

Если вы ожидаете, что вернуть много записей (и LIMIT их), вам нужно использовать индекс для сортировки, а затем фильтр:

CREATE INDEX ix_a_b ON a (b); 
CREATE INDEX ix_b_c ON b (c) 

SELECT * 
FROM B FORCE INDEX (ix_b_c) 
JOIN A 
ON  A.b = B.b 
ORDER BY 
     b.c 
LIMIT 10; 

1, 'SIMPLE', 'B', 'index', '', 'ix_b_c', '4', '', 2, 'Using index' 
1, 'SIMPLE', 'A', 'ref', 'ix_a_b', 'ix_a_b', '4', 'test.B.b', 4, 'Using index' 
+0

С реальными данными таблица записей довольно большая (как по ширине, так и по количеству строк, с большим количеством VARCHAR (255): s), и поэтому временная таблица стоит дороже, так как есть много данных для копирования. На нашем производстве db (8-ядерный xeon со всем в памяти) запрос занимает около 0,05-0,1 с, а MV-тест показывает sub 0,01 сек. – Paso

+0

Я не получаю тот же план запроса, который вы напечатали выше для одного и того же запроса. В любом случае, изменение ORDER действительно не помогает мне, уверен, что он удаляет файлы, но я получаю результаты в неправильном порядке! Кроме того, просто изменение ORDER в исходном запросе на «B.b, B.c» удаляет файлort, указывающий (ну, для меня;)), что это можно сделать без временной таблицы/filesort. (Забавно, я фактически заимствовал SP для вставки из вашего блога) – Paso

+0

@Paso: Извините, я не очень хорошо понял вашу задачу. Создайте индекс только на 'b.c' и измените условие' ORDER BY'. Я сейчас обновлю его в сообщении. – Quassnoi

0

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;

Если вы псевдоним столбцов, это поможет ? Пример:

SELECT 
T1.a AS colA, 
T2.b AS colB, 
T2.c AS colC 
FROM A AS T1 
JOIN B AS T2 
ON (T1.b = T2.b) 
WHERE 
T1.a = 44 
ORDER BY colC; 

Единственные изменения, которые я сделал, были:

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

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

+0

Боюсь, что нет, ваш запрос дает точно такой же результат EXPLAIN. – Paso

+0

Вы действительно хотите присоединиться к двум таблицам? Я имею в виду, соединяются ли две таблицы, где каждая строка является полным результатом на основе запроса, или больше похожа на каждую строку, имеющую данные, необходимые из обеих таблиц? Я спрашиваю, потому что, если две таблицы фактически не связаны друг с другом таким образом, что требуется соединение, вы можете вместо этого использовать UNION. С UNION запросы полностью независимы и, следовательно, никаких подзапросов или временных таблиц или чего-либо еще не требуется. – Anthony

+0

Я действительно не понимаю. Таблицы JOINed над A.b = B.b, и мне нужны данные из B для каждого A, соответствующего условию, как будет помогать UNION здесь? Для полноты; нет Мне не нужны все данные, только данные из B. См. пример тега в верхней части вопроса, который должен объяснить все как можно точнее. – Paso