当加入一个非常小/空的表时,为什么MySQL会在我使用“限制”的情况下进行完全扫描?

当加入一个非常小/空的表时,为什么MySQL会在我使用“限制”的情况下进行完全扫描?,mysql,sql,performance,Mysql,Sql,Performance,编辑:我从示例查询中删除了GROUPBY子句,但是当我将表x连接到一个空的/1行表y时,同样的问题会出现。尽管我使用了limit,MySQL还是对表x进行了完整的表扫描 原始问题: 我试图学习如何优化SQL查询,但遇到了一个我无法理解的行为。有这样的模式 CREATE TABLE `event` ( `ev_id` int(11) NOT NULL AUTO_INCREMENT, `ev_note` varchar(255) DEFAULT NULL, PRIMARY KEY

编辑:我从示例查询中删除了GROUPBY子句,但是当我将表x连接到一个空的/1行表y时,同样的问题会出现。尽管我使用了limit,MySQL还是对表x进行了完整的表扫描

原始问题: 我试图学习如何优化SQL查询,但遇到了一个我无法理解的行为。有这样的模式

CREATE TABLE `event` (
   `ev_id` int(11) NOT NULL AUTO_INCREMENT,
   `ev_note` varchar(255) DEFAULT NULL,
   PRIMARY KEY (`ev_id`)
 ) ENGINE=InnoDB;

CREATE TABLE `table1` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(45) DEFAULT NULL,   
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB ;

CREATE TABLE `table2` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(45) DEFAULT NULL,   
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB ;
查询1快速

说明

+---+-----------+---------------+------+-----------------------------------+----------+---------+---------------+------+-----------+
|id |select_type|table          | type | possible_keys                     | key      | key_len | ref           | rows | Extra     |
+---+-----------+---------------+------+-----------------------------------+----------+---------+---------------+------+-----------+
|1  |SIMPLE     |users          |index |PRIMARY,fk_country_idx             | PRIMARY  |4        |               |2     |           |
|1  |SIMPLE     |user_school_mm |ref   |PRIMARY,fk_user_school_mm_user_idx | PRIMARY  |4        |tests.users.id |1     |Using index|
+---+-----------+---------------+------+-----------------------------------+----------+---------+---------------+------+-----------+ 
查询2慢

说明

+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys          | key | key_len | ref | rows | Extra                                             |
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|1  |SIMPLE     |users   |ALL   | PRIMARY,fk_country_idx |     |         |     | 10   | Using temporary; Using filesort                   |
|1  |SIMPLE     |country |ALL   | PRIMARY                |     |         |     | 1    | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref              | rows | Extra  |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|1  |SIMPLE     |event   |index |                |PRIMARY |4        |                  | 2    |        |
|1  |SIMPLE     |table2  |eq_ref|PRIMARY         |PRIMARY |4        |tests.event.ev_id | 1    |        |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref   | rows    | Extra                                             |
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|1  |SIMPLE     |event   |ALL   |                |        |         |       |33506704 | Using temporary; Using filesort                   |
|1  |SIMPLE     |table1  |ALL   |PRIMARY         |        |         |       |1        | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
我不明白幕后发生了什么,我想如果我使用users表的主键进行排序和分组,MySQL将获取users表的前2行并继续连接,但它似乎没有这样做,并在查询2中扫描了整个表

为什么MySQL扫描query2中的整个表,而只扫描query1中的前两行


MySQL版本是5.6.38

经过一些测试,如果第二个tableuser\u school\u mm有一些数据,MySQL不会对第一个表进行全表扫描,如果第二个tablecountry没有数据/数据很少,MySQL会对1或2条记录进行全表扫描。为什么会发生这种情况?我不知道

如何繁殖

1-创建这样的模式

CREATE TABLE `event` (
   `ev_id` int(11) NOT NULL AUTO_INCREMENT,
   `ev_note` varchar(255) DEFAULT NULL,
   PRIMARY KEY (`ev_id`)
 ) ENGINE=InnoDB;

CREATE TABLE `table1` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(45) DEFAULT NULL,   
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB ;

CREATE TABLE `table2` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(45) DEFAULT NULL,   
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB ;
2-在主表事件中插入一些我用35601000行填充的数据

3-将表1留空,并在表2中插入15行

insert into table2 (name) values 
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar'),
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar'),
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar');
4-现在将主表与表2连接起来,并用表1重新测试相同的查询

查询1快速

说明

+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys          | key | key_len | ref | rows | Extra                                             |
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|1  |SIMPLE     |users   |ALL   | PRIMARY,fk_country_idx |     |         |     | 10   | Using temporary; Using filesort                   |
|1  |SIMPLE     |country |ALL   | PRIMARY                |     |         |     | 1    | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref              | rows | Extra  |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|1  |SIMPLE     |event   |index |                |PRIMARY |4        |                  | 2    |        |
|1  |SIMPLE     |table2  |eq_ref|PRIMARY         |PRIMARY |4        |tests.event.ev_id | 1    |        |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref   | rows    | Extra                                             |
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|1  |SIMPLE     |event   |ALL   |                |        |         |       |33506704 | Using temporary; Using filesort                   |
|1  |SIMPLE     |table1  |ALL   |PRIMARY         |        |         |       |1        | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
查询2慢

说明

+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys          | key | key_len | ref | rows | Extra                                             |
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|1  |SIMPLE     |users   |ALL   | PRIMARY,fk_country_idx |     |         |     | 10   | Using temporary; Using filesort                   |
|1  |SIMPLE     |country |ALL   | PRIMARY                |     |         |     | 1    | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref              | rows | Extra  |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|1  |SIMPLE     |event   |index |                |PRIMARY |4        |                  | 2    |        |
|1  |SIMPLE     |table2  |eq_ref|PRIMARY         |PRIMARY |4        |tests.event.ev_id | 1    |        |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref   | rows    | Extra                                             |
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|1  |SIMPLE     |event   |ALL   |                |        |         |       |33506704 | Using temporary; Using filesort                   |
|1  |SIMPLE     |table1  |ALL   |PRIMARY         |        |         |       |1        | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+

MySQL版本是5.6.38

经过一些测试,如果第二个tableuser\u school\u mm有一些数据,MySQL不会对第一个表进行全表扫描,如果第二个tablecountry没有数据/数据很少,MySQL会对1或2条记录进行全表扫描。为什么会发生这种情况?我不知道

如何繁殖

1-创建这样的模式

CREATE TABLE `event` (
   `ev_id` int(11) NOT NULL AUTO_INCREMENT,
   `ev_note` varchar(255) DEFAULT NULL,
   PRIMARY KEY (`ev_id`)
 ) ENGINE=InnoDB;

CREATE TABLE `table1` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(45) DEFAULT NULL,   
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB ;

CREATE TABLE `table2` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(45) DEFAULT NULL,   
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB ;
2-在主表事件中插入一些我用35601000行填充的数据

3-将表1留空,并在表2中插入15行

insert into table2 (name) values 
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar'),
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar'),
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar');
4-现在将主表与表2连接起来,并用表1重新测试相同的查询

查询1快速

说明

+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys          | key | key_len | ref | rows | Extra                                             |
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|1  |SIMPLE     |users   |ALL   | PRIMARY,fk_country_idx |     |         |     | 10   | Using temporary; Using filesort                   |
|1  |SIMPLE     |country |ALL   | PRIMARY                |     |         |     | 1    | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref              | rows | Extra  |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|1  |SIMPLE     |event   |index |                |PRIMARY |4        |                  | 2    |        |
|1  |SIMPLE     |table2  |eq_ref|PRIMARY         |PRIMARY |4        |tests.event.ev_id | 1    |        |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref   | rows    | Extra                                             |
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|1  |SIMPLE     |event   |ALL   |                |        |         |       |33506704 | Using temporary; Using filesort                   |
|1  |SIMPLE     |table1  |ALL   |PRIMARY         |        |         |       |1        | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
查询2慢

说明

+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys          | key | key_len | ref | rows | Extra                                             |
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|1  |SIMPLE     |users   |ALL   | PRIMARY,fk_country_idx |     |         |     | 10   | Using temporary; Using filesort                   |
|1  |SIMPLE     |country |ALL   | PRIMARY                |     |         |     | 1    | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref              | rows | Extra  |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|1  |SIMPLE     |event   |index |                |PRIMARY |4        |                  | 2    |        |
|1  |SIMPLE     |table2  |eq_ref|PRIMARY         |PRIMARY |4        |tests.event.ev_id | 1    |        |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|id |select_type|table   | type | possible_keys  | key    | key_len | ref   | rows    | Extra                                             |
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|1  |SIMPLE     |event   |ALL   |                |        |         |       |33506704 | Using temporary; Using filesort                   |
|1  |SIMPLE     |table1  |ALL   |PRIMARY         |        |         |       |1        | Using where; Using join buffer (Block Nested Loop)|
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+

MySQL版本是5.6.38

主要原因是误用了GROUP BY。让我们来看第一个问题。尽管速度很快,但仍然是错误的:

SELECT * 
    FROM users
    LEFT JOIN user_school_mm on users.id = user_school_mm.user_id
    GROUP BY users.id
    ORDER BY users.id ASC
    LIMIT 2
一个用户可以上两所学校。使用多:多映射的用户_school_mm声称这是一种可能性。因此,在完成联接之后,您可以为单个用户获得两行。但是,您可以按users.id分组,将其归结为一行。但是您应该使用两个学校id值中的哪一个


在您提出有意义的查询之前,我不会尝试解决性能问题。在这一点上,可以更容易地指出为什么一个查询比另一个查询性能更好。

主要原因是GROUP BY的误用。让我们来看第一个问题。尽管速度很快,但仍然是错误的:

SELECT * 
    FROM users
    LEFT JOIN user_school_mm on users.id = user_school_mm.user_id
    GROUP BY users.id
    ORDER BY users.id ASC
    LIMIT 2
一个用户可以上两所学校。使用多:多映射的用户_school_mm声称这是一种可能性。因此,在完成联接之后,您可以为单个用户获得两行。但是,您可以按users.id分组,将其归结为一行。但是您应该使用两个学校id值中的哪一个


在您提出有意义的查询之前,我不会尝试解决性能问题。在这一点上,可以更容易地指出为什么一个查询比另一个查询性能更好。

MySQL优化器将首先决定连接顺序/方法,然后检查对于所选的连接顺序,是否可以使用索引避免排序。对于这个问题中的慢速查询,优化器决定使用块嵌套循环BNL join

当其中一个表非常小且没有限制时,BNL通常比使用索引更快

但是,使用BNL,行不一定按照第一个表给出的顺序出现。因此,在应用限制之前,需要对联接结果进行排序


您可以通过设置优化器_开关='block_nested_loop=off'来关闭BNL

MySQL优化器将首先决定连接顺序/方法,然后检查对于所选的连接顺序,是否可以使用索引避免排序。对于这个问题中的慢速查询,优化器决定使用块嵌套循环BNL join

当其中一个表非常小且没有限制时,BNL通常比使用索引更快

但是,使用BNL,行不一定按照第一个表给出的顺序出现。因此,在应用限制之前,需要对联接结果进行排序


您可以通过设置优化器_开关='block_nested_loop=off'来关闭BNL

我猜一旦找到足够的行,它就会结束扫描。@shawnt00但在查询2中,它扫描了整个表,在这个示例中有10行,如果用户选项卡上有300万行
le是300万行你的好查询也不好。。。选择*。。。。GROUP BY users.id是错误的ansi GROUP BY SQL。使用select*和group by,可能会导致错误的不相关数据—这些是格式错误的查询。也许这解释了为什么MySQL不尝试优化它们。这些都很容易被更合适的逻辑所取代,从而产生正确的执行计划。很高兴你把它整理好了。实际上,我只是第一次浏览了一下,错过了一个重要的细节。我猜一旦找到足够多的行,扫描就会结束。@shawnt00但在查询2中,它扫描了整个表,在这个示例中有10行,如果用户表是300万行,则扫描了300万行。好的查询也不好。。。选择*。。。。GROUP BY users.id是错误的ansi GROUP BY SQL。使用select*和group by,可能会导致错误的不相关数据—这些是格式错误的查询。也许这解释了为什么MySQL不尝试优化它们。这些都很容易被更合适的逻辑所取代,从而产生正确的执行计划。很高兴你把它整理好了。事实上,我只是第一次浏览了一下,错过了一个重要的细节。非常感谢您的关注,我删除了群组,MySQL仍然会做出同样的行为。如果我将表连接到一个空表,MySQL将对第一个表进行完全扫描。我在答案中提供了如何复制的详细步骤。非常感谢您的关注,我删除了Groupby,MySQL仍然会做出相同的行为。如果我将表连接到一个空表,MySQL将对第一个表进行完全扫描。我在回答如何复制时提供了详细的步骤。你……是……国王。从现在起,我会读你说的每一个字。非常感谢您的回答,也感谢您的MySQL。您……是……国王。从现在起,我会读你说的每一个字。非常感谢您的回答,也感谢您的MySQL。