GROUP BY
子句,但同样的问题显示“当我将表x连接到空/ 1行表时,MySQL在表x上进行全表扫描,尽管我'使用限制“
原始问题: 我试图学习如何优化我的SQL查询,我遇到了一个我无法理解的行为。有这样的架构
CREATE TABLE `country` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB ;
CREATE TABLE `school` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB ;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
`country_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_country_idx` (`country_id`),
CONSTRAINT `fk_users_country` FOREIGN KEY (`country_id`) REFERENCES `country` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB ;
CREATE TABLE `user_school_mm` (
`user_id` int(11) NOT NULL,
`school_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`, `school_id`),
KEY `fk_user_school_mm_user_idx` (`user_id`),
KEY `fk_user_school_mm_school_idx` (`school_id`),
CONSTRAINT `fk_user_school_mm_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `fk_user_school_mm_school` FOREIGN KEY (`school_id`) REFERENCES `school` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB ;
INSERT INTO country (name) VALUES ('fooCountry1');
INSERT INTO school (name) VALUES ('fooSchool1'),('fooSchool2'),('fooSchool3');
INSERT INTO users (name, country_id) VALUES
('fooUser1',1),
('fooUser2',1),
('fooUser3',1),
('fooUser4',1),
('fooUser5',1),
('fooUser6',1),
('fooUser7',1),
('fooUser8',1),
('fooUser9',1),
('fooUser10',1)
;
INSERT INTO user_school_mm (user_id, school_id) VALUES
(1,1),(1,2),(1,3),
(2,1),(2,2),(2,3),
(3,1),(3,2),(3,3),
(4,1),(4,2),(4,3),
(5,1),(5,2),(5,3),
(6,1),(6,2),(6,3),
(7,1),(7,2),(7,3),
(8,1),(8,2),(8,3),
(9,1),(9,2),(9,3),
(10,1),(10,2),(10,3)
;
QUERY 1(快速)
-- GOOD QUERY (MySQL uses the limit and skip users table scan after 2 rows )
SELECT *
FROM
users LEFT JOIN
user_school_mm on users.id = user_school_mm.user_id
ORDER BY users.id ASC
LIMIT 2
-- takes about 100 milliseconds if users table is 3 million records
解释
+---+-----------+---------------+------+-----------------------------------+----------+---------+---------------+------+-----------+
|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|
+---+-----------+---------------+------+-----------------------------------+----------+---------+---------------+------+-----------+
QUERY 2(慢)
-- BAD QUERY (MySQL ignores the limit and scanned the entire users table )
SELECT *
FROM
users LEFT JOIN
country on users.country_id = country.id
ORDER BY users.id ASC
LIMIT 2
-- takes about 9 seconds if users table is 3 million records
解释
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
|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)|
+---+-----------+--------+------+------------------------+-----+---------+-----+------+---------------------------------------------------+
我不明白幕后发生了什么,我想如果我使用users表的主键进行排序和分组,MySQL会占用users表的前两行并继续加入,但它似乎没有这样做,并在查询2中扫描整个表
为什么MySQL在查询2中扫描了整个表,而在查询1中只占用了前两行?
MySQL版本为5.6.38
答案 0 :(得分:3)
MySQL优化器将首先决定连接顺序/方法,然后检查对于所选的连接顺序,是否可以避免使用索引进行排序。对于此问题中的慢查询,优化器已决定使用Block-Nested-Loop(BNL)连接。当其中一个表非常小(并且没有LIMIT)时,BNL通常比使用索引更快。但是,对于BNL,行不一定按第一个表给出的顺序排列。因此,在应用LIMIT之前,需要对连接的结果进行排序。
你可以通过“set optimizer_switch ='block_nested_loop = off'”来关闭BNL
答案 1 :(得分:2)
主要原因是滥用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
用户可以去两所学校。使用许多:许多映射user_school_mm
声称这是可能的。因此,在执行JOIN
之后,您将为单个用户获得2行。但是,你GROUP BY users.id
,把它煮成一排。但是......你应该使用哪两个school_id值?
在您提出有意义的查询之前,我不会尝试解决性能问题。此时,更容易指出为什么一个查询比另一个查询表现更好。
答案 2 :(得分:0)
经过一些测试后发现,如果第二个表(user_school_mm
)有一些数据,MySQL将不会在第一个表上进行全表扫描,如果第二个表(country
)没有数据/非常少的数据(1或2条记录)MySQL将进行全表扫描。为什么会这样?我不知道。
如何重现
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-在主表中插入(在本例中为event
)一些数据(我用35601000行填充)
3-将table1留空并在table2中插入15行
insert into table2 (name) values
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar'),
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar'),
('fooBar'),('fooBar'),('fooBar'),('fooBar'),('fooBar');
4-现在使用table2加入主表,并使用table1重新测试相同的查询
查询1(快速)
select *
from
event left join
table2 on event.ev_id = table2.id
order by event.ev_id
limit 2;
-- executed in 300 milliseconds measured by the client
解释
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
|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 | |
+---+-----------+--------+------+----------------+--------+---------+------------------+------+--------+
查询2(慢)
select *
from
event left join
table1 on event.ev_id = table1.id
order by event.ev_id
limit 2;
-- executed in 79 seconds measured by the client
解释
+---+-----------+--------+------+----------------+--------+---------+-------+---------+---------------------------------------------------+
|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