索引优化案例分析
单表:
create table if not exists article(
id int unsigned not null primary key auto_increment,
author_id int unsigned not null,
category_id int unsigned not null,
views int unsigned not null,
comments int unsigned not null,
title varchar(255) not null,
content text not null
);
insert into article(author_id,category_id,views,comments,title,content)
values
(1,1,1,1,'1','1'),
(2,2,2,2,'2','2'),
(1,1,3,3,'3','3');
查询category_id是1且comments>1的情况下,views最多的id.
select id,author_id from article
where category_id = 1 and comments > 1
order by views desc
limit 1;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | article | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
(范围之后的索引会导致失效)
1.create index idx_article_ccv on article(category_id,comments,views);
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | article | NULL | range | idx_article_ccv | idx_article_ccv | 8 | NULL | 1 | 100.00 | Using index condition; Using filesort |
+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+
1 row in set, 1 warning (0.07 sec)
因为结果中有using filesort,因此本次的索引不合适
2.drop index idx_article_ccv on article;
去掉comments的索引
create index idx_article_cv on article(category_id,views);
双表:
商品类别class
create table if not exists class(
id int not null primary key auto_increment,
card int not null
);
商品book
create table if not exists book(
book_id int not null primary key auto_increment,
card int not null
);
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into class(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
查询:
select * from book inner join class on book.card = class.card;
select * from book left join class on book.card = class.card;
select * from book fight join class on book.card = class.card;
mysql> explain select * from book right join class on book.card = class.card;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | NULL |
| 1 | SIMPLE | book | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
问题:索引应该建在哪张表?
尝试在book表建索引:alter table book add index idx_b_c(card);
mysql> explain select * from class left join book on book.card = class.card;
+----+-------------+-------+------------+------+---------------+---------+---------+--------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+---------+---------+--------------------+------+----------+-------------+
| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | NULL |
| 1 | SIMPLE | book | NULL | ref | idx_b_c | idx_b_c | 4 | db_name.class.card | 1 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+---------+---------+--------------------+------+----------+-------------+
左链接加在右表索引是正确的。
结论:左连接或者右连接都在相反的方向加索引。
简单点理解就是,如果是左连接,坐标一定全包含,因此只需要右表查询快就好。
三表:
新增表phone
create table if not exists phone(
phone_id int not null primary key auto_increment,
card int not null
);
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
insert into book(card) value(floor(1+rand()*20));
问题:3表连接索引应该建在哪张表?
explain select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
mysql> explain select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | NULL |
| 1 | SIMPLE | book | NULL | ALL | NULL | NULL | NULL | NULL | 40 | 100.00 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | phone | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
尝试:左连接时在右侧的两张表建索引
mysql> alter table book add index idx_b_c(card);
Query OK, 0 rows affected (0.53 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table phone add index idx_p_c(card);
Query OK, 0 rows affected (0.39 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
+----+-------------+-------+------------+------+---------------+---------+---------+--------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+---------+---------+--------------------+------+----------+-------------+
| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | NULL |
| 1 | SIMPLE | book | NULL | ref | idx_b_c | idx_b_c | 4 | db_name.class.card | 2 | 100.00 | Using index |
| 1 | SIMPLE | phone | NULL | ref | idx_p_c | idx_p_c | 4 | db_name.book.card | 1 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+---------+---------+--------------------+------+----------+-------------+
结论:尽量减少join中nestedlook的循环次数,永远用小表驱动大表。