MySQL не использует индекс с JOIN, WHERE и ORDER
-
22-07-2019 - |
Вопрос
У нас есть две таблицы, напоминающие простую структуру тег-запись следующим образом (на самом деле все гораздо сложнее, но в этом и есть суть проблемы):
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
Проблема заключается в получении упорядоченных записей с определенным тегом.Очевидный способ сделать это — с помощью простого соединения и индексов (PK)(A.a, A.b), (A.b), (PK)(B.b), (B.b,B.c) как таковых:
select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;
Однако это дает неприятный результат сортировки файлов:
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
| 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;
Но мы уверены, что справимся с этой проблемой, если проблема с сортировкой файлов исчезнет.
Кто-нибудь знает, почему простое соединение в кодовом блоке 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;
Решение
Когда я пытаюсь воспроизвести этот запрос, используя ваши скрипты:
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
для фильтрации, а затем сортируйте с помощью сортировки файлов (как это делает запрос выше).
Если вы ожидаете, что он вернет много записей (и 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'
Другие советы
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;
Единственные изменения, которые я внес:
- Я поместил условия соединения в скобки
- Условия соединения и условия, основанные на столбцах таблицы.
- Условие ORDER BY основано на результирующем столбце таблицы.
- Я назначал псевдонимы столбцам таблицы результатов и запрашиваемым таблицам, чтобы (надеюсь) сделать более понятным, когда я использую тот или иной вариант (и более понятным для сервера).Вы пренебрегаете ссылкой на свои столбцы в двух местах исходного запроса).
Я знаю, что ваши реальные данные более сложны, но я предполагаю, что вы предоставили простую версию запроса, потому что проблема находится на этом простом уровне.