在不同的表/列上喜欢和排序的地方

时间:2016-05-28 09:03:13

标签: mysql sql query-optimization

有关信息,请参阅以下示例,big_table由数百万行和small_table数百行组成。

以下是我正在尝试的基本查询:

SELECT b.id 
    FROM big_table b 
    LEFT JOIN small_table s 
    ON b.small_id=s.id
    WHERE s.name like 'something%' 
    ORDER BY b.name 
    LIMIT 10, 10;

这很慢,我可以理解为什么两个索引都不能使用。

我最初的想法是将查询分成几部分。

这很快:

SELECT id FROM small_table WHERE name like 'something%';

这也很快:

SELECT id FROM big_table WHERE small_id IN (1, 2) ORDER BY name LIMIT 10, 10;

但是,放在一起,它变得缓慢:

SELECT id FROM big_table 
    WHERE small_id 
    IN (
        SELECT id 
        FROM small_table WHERE name like 'something%'
    ) 
    ORDER BY name 
    LIMIT 10, 10;

除非为每一行重新评估子查询,否则它应该比单独执行两个查询的速度慢吗?

我正在寻找任何帮助来优化初始查询并理解为什么第二个查询不起作用。

上次查询的EXPLAIN结果:

| id   | select_type | table | type | possible_keys | key | key_len | ref | rows  | Extra
| 1 | PRIMARY | small_table | range | PRIMARY, ix_small_name | ix_small_name | 768 | NULL | 1 | Using where; Using index; Using temporary; Using filesort |
| 1 | PRIMARY | big_table | ref | ix_join_foreign_key   | ix_join_foreign_key | 9 | small_table.id | 11870 | |

临时解决方案:

SELECT id FROM big_table ignore index(ix_join_foreign_key)
    WHERE small_id 
    IN (
        SELECT id 
        FROM small_table ignore index(PRIMARY)
        WHERE name like 'something%'
    ) 
    ORDER BY name 
    LIMIT 10, 10;

(结果和解释与EXISTS相同而不是IN)

EXPLAIN输出变为:

| 1 | PRIMARY | big_table | index  | NULL | ix_big_name | 768 | NULL | 20 | |
| 1 | PRIMARY | <subquery2> | eq_ref | distinct_key | distinct_key | 8 | func | 1 | |
| 2 | MATERIALIZED | small_table | range | ix_small_name | ix_small_name | 768 | NULL | 1 | Using where; Using index |

如果有人有更好的解决方案,我仍然感兴趣。

4 个答案:

答案 0 :(得分:1)

您正在寻找EXISTSIN查询。由于已知MySQL在IN上较弱,我尝试EXISTS,尽管更喜欢IN更简单。

select id
from big_table b
where exists
(
  select *
  from small_table s
  where s.id = b.small_id
  and s.name = 'something%'
)
order by name 
limit 10, 10;

big_table上建立一个好的索引会很有帮助。它应首先包含small_id以查找匹配项,然后包含name以进行排序。据我所知,该ID自动包含在MySQL索引中(否则它也应该添加到索引中)。因此,您有一个索引,其中包含所需顺序的big_table所需的所有字段(称为覆盖索引),因此所有数据都可以单独从索引中读取,而表本身并不是;必须被访问。

create index idx_big_quick on big_table(small_id, name);

答案 1 :(得分:1)

您遇到的问题是您在小表上有条件但是试图避免在大表中进行排序。在MySQL中,我认为你至少需要进行全表扫描。

一步是使用exists编写查询,正如其他人提到的那样:

SELECT b.id
FROM big_table b
WHERE EXISTS (SELECT 1
              FROM small_table s
              WHERE s.name LIKE 'something%' AND s.id = b.small_id
             )
ORDER BY b.name;

问题是:你可以使用索引欺骗MySQL进行ORDER BY吗?一种可能性是使用适当的索引。在这种情况下,适当的索引是:big_table(name, small_id, id)small_table(id, name)。索引中键的排序很重要。因为第一个是覆盖索引,所以MySQL可以按名称顺序读取索引,选择适当的ID。

答案 2 :(得分:0)

你可以试试这个:

SELECT b.id
FROM big_table b
JOIN small_table s
  ON b.small_id = s.id
WHERE s.name like 'something%'
ORDER BY b.name;

SELECT b.id FROM big_table b
WHERE EXISTS(SELECT 1 FROM small_table s
             WHERE s.name LIKE 'something%' AND s.id = b.small_id)
ORDER BY b.name;

注意:您似乎不需要LEFT JOIN。左外连接几乎总是会导致big_table

的全表扫描

PS确保您在big_table.small_id

上有索引

答案 3 :(得分:0)

计划A

SELECT  b.id
    FROM  big_table b
    JOIN  small_table s  ON b.small_id=s.id
    WHERE  s.name like 'something%'
    ORDER BY  b.name
    LIMIT  10, 10;

(请注意删除LEFT。)

你需要

small_table:  INDEX(name, id)
big_table:    INDEX(small_id), or, for 'covering': INDEX(small_id, name, id)

它将使用s索引查找'something%'并继续浏览。但它必须找到所有这些行,JOINb才能找到所有这些行。只有这样才能执行ORDER BYOFFSETLIMIT成为一个文件出口(可能发生在RAM中)。

索引中的列顺序很重要。

计划B

另一个建议可能运作良好;这取决于各种各样的事情。

SELECT  b.id
    FROM  big_table b
    WHERE  EXISTS 
      ( SELECT  *
            FROM  small_table s
            WHERE  s.name LIKE 'something%'
              AND  s.id = b.small_id 
      )
    ORDER BY  b.name
    LIMIT 10, 10;

需要这些:

big_table:    INDEX(name), or for 'covering',  INDEX(name, small_id, id)
small_table:  INDEX(id, name), which is 'covering'

(警告:如果你做的不是SELECT b.id,我对覆盖的评论可能是错误的。)

哪个更快(A或B)?如果不理解“某事物%”的频率以及多对一映射的“多少”,就无法预测。

设置

如果这些表是InnoDB,请确保将innodb_buffer_pool_size设置为可用 RAM的大约70%。

<强>分页

您使用OFFSET意味着您正在“分页”数据? OFFSET是一种效率低下的方法。请参阅my blog,但请注意,只有B计划可以使用它。