MySQL不使用带有连接、位置和顺序的索引

MySQL不使用带有连接、位置和顺序的索引,mysql,join,indexing,sql-order-by,materialized-views,Mysql,Join,Indexing,Sql Order By,Materialized Views,我们有两个类似于简单标记记录结构的表,如下所示(实际上要复杂得多,但这是问题的本质): 及 问题是获取带有特定标记的有序记录。最明显的方法是使用一个简单的连接和索引(PK)(a.a,a.b),(a.b),(PK)(b.b),(b.b,b.c),例如: 但是,这会导致文件排序的不愉快结果: +----+-------------+-------+------+---------------+---------+---------+-----------+------+---------------

我们有两个类似于简单标记记录结构的表,如下所示(实际上要复杂得多,但这是问题的本质):

问题是获取带有特定标记的有序记录。最明显的方法是使用一个简单的连接和索引(PK)(a.a,a.b),(a.b),(PK)(b.b),(b.b,b.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进行相同的查询)

使情况进一步复杂化的是,我们在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;
删除表格(如果存在);
删除表格(如果存在);
如果存在,则删除表C;
创建临时表A(A INT NOT NULL,b INT NOT NULL,主键(A,b),索引idx_A_b(b))ENGINE=INNODB;
创建临时表B(B INT NOT NULL,c INT NOT NULL,d VARCHAR(5000)NOT NULL默认值“”,主键(B),索引idx_B_c(c),索引idx_B_B(B,c))引擎=INNODB;
分隔符$$
创建过程prc_填充(cnt INT)
开始
声明_cntint;
设置_cnt=1;

而_cnt当我尝试使用您的脚本重现此查询时:

SELECT  A.a, A.b, B.c
FROM    A
JOIN    B
ON      A.b = B.b
WHERE   a = 44
ORDER BY
        c
,它在
0.0043秒
(立即)内完成,返回
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进行排序(与上面的查询一样)

如果您希望它返回许多记录(并且
限制它们),则需要使用索引进行排序,然后进行筛选:

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'

从A.b=b.b上的连接b中选择A.A、A.b、b.c,其中A=44按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;
我所做的唯一改变是:

  • 我把连接条件放在括号里
  • 连接条件和where条件基于表列
  • ORDER BY条件基于生成的表格列
  • 我给结果表列和查询表添加了别名(希望如此),以便在使用其中一个时更加清楚(对服务器更加清楚。您在原始查询中忽略了在两个位置引用列)

我知道您的真实数据更复杂,但我假设您提供了一个简单的查询版本,因为问题就在这个简单的级别。

对于真实数据,记录表非常大(宽度和行数都很大,有很多VARCHAR(255):s),因此临时表的成本更高,因为要复制的数据更多。在我们的生产数据库(内存中有所有内容的8核xeon)上,查询大约需要0.05-0.1s,MV测试显示小于0.01s的时间。对于相同的查询,我没有得到与上面打印的相同的查询计划。无论如何,顺序的改变并没有真正帮助我,当然它删除了文件排序,但我得到的结果顺序错误!另外,只需将原始查询中的顺序更改为“B.B,B.c”即可删除文件排序,这表明(对我来说;)不需要临时表/文件排序就可以完成此操作。(有趣的是,我实际上是从你的博客上借了SP来插入的)@Paso:对不起,我不太理解你的任务。仅在
b.c
上创建索引,并根据
条件更改
顺序。我现在会在帖子中更新它。@Paso:我注意到:)很高兴听到你读我的博客。有一点需要注意:在过程中填充
InnoDB
表时,总是在事务中进行,这样会更快。@Paso:您能告诉我在运行查询时得到了哪些计划吗?恐怕不行,您的查询给出了完全相同的解释结果。您真的想加入这两个表吗?我的意思是,这两个表是否连接起来,其中每一行都是基于查询的完整结果,或者更像是每一行都有两个表所需的数据?我问,因为如果这两个表实际上没有以这样的方式绑定在一起,那么就需要考虑一个联合。有了联合,查询是完全独立的,因此不需要子查询、临时表或任何其他繁重的事情。我真的不明白。这些表是通过A.b=b.b进行连接的,我需要b中的数据来表示与A条件匹配的每个A,联合在这里有什么帮助?完整性;不,我不需要所有的数据,只需要B中的数据。请参阅问题顶部的标记示例,这应该尽可能精确地解释所有内容。文件排序发生在ORDER BY子句中。
B.c
是如何编制索引的?@jason:我已经更新了帖子中的SQL,使其更具可读性。索引现在应该清楚了。
SELECT  A.a, A.b, B.c
FROM    A
JOIN    B
ON      A.b = B.b
WHERE   a = 44
ORDER BY
        c
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, ''
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 
 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;