我们有两个类似于简单标记记录结构的表格如下(实际上它更复杂,但这是问题的本质):
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;
答案 0 :(得分: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'
答案 1 :(得分:1)
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;
我所做的唯一改变是:
我知道您的真实数据更复杂,但我认为您提供了一个简单版本的查询,因为问题就在于这个简单的级别。