CREATE%20INDEX%20vote%20ON%20vote_record(vote_num);/*索引信息*/--------------
Table %20 %20 %20 %20Non_unique %20Key_name %20Seq_in_index %20Column_name %20Collation %20Cardinality %20Sub_part %20Packed %20Null %20 %20Index_type %20Comment %20Index_comment
----------- %20---------- %20-------- %20------------ %20----------- %20--------- %20----------- %20-------- %20------ %20------ %20---------- %20------- %20---------------
vote_record %20 %20 %20 %20 %20 0 %20PRIMARY %20 %20 %20 %20 %20 %20 %201 %20id %20 %20 %20 %20 %20 A %20 %20 %20 %20 %20 %20 %20 996507 %20 %20(NULL) %20(NULL) %20 %20 %20 %20 %20BTREE %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20
vote_record %20 %20 %20 %20 %20 1 %20votenum %20 %20 %20 %20 %20 %20 %201 %20vote_num %20 %20 A %20 %20 %20 %20 %20 %20 %20 %20 9942 %20 %20(NULL) %20(NULL) %20 %20 %20 %20 %20BTREE %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20
/*DDL%20信息*/------------
CREATE%20TABLE%20`vote_record`%20(
%20`id`%20int(10)%20unsigned%20NOT%20NULL%20AUTO_INCREMENT,
%20`user_id`%20varchar(20)%20NOT%20NULL%20DEFAULT%20''%20COMMENT%20'用户Id',
%20`vote_num`%20int(10)%20unsigned%20NOT%20NULL%20DEFAULT%20'0'%20COMMENT%20'投票数',
%20`group_id`%20int(10)%20unsigned%20NOT%20NULL%20DEFAULT%20'0'%20COMMENT%20'用户组id%200-未激活用户%201-普通用户%202-vip用户%203-管理员用户',
%20`status`%20tinyint(2)%20unsigned%20NOT%20NULL%20DEFAULT%20'1'%20COMMENT%20'状态%201-正常%202-已删除',
%20`create_time`%20datetime%20NOT%20NULL%20DEFAULT%20'0000-00-00%2000:00:00'%20COMMENT%20'创建时间',
%20PRIMARY%20KEY%20(`id`),
%20KEY%20`votenum`%20(`vote_num`)
)%20ENGINE=InnoDB%20AUTO_INCREMENT=1000000%20DEFAULT%20CHARSET=utf8%20COMMENT='投票记录表'
explain查看执行计划
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE vote_record \N ALL votenum,vote \N \N \N 996507 50.00 Using%20where
还是没用到索引,因为不符合最左前缀匹配。查询需要3.5秒左右
最后修改一下sql语句
EXPLAIN%20SELECT%20*%20FROM%20vote_record%20WHERE%20id%20>%200%20AND%20vote_num%20>%201000;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE vote_record \N range PRIMARY,votenum,vote PRIMARY 4 \N 498253 50.00 Using%20where
用到了索引,但是只用到了主键索引。再修改一次
EXPLAIN%20SELECT%20*%20FROM%20vote_record%20WHERE%20id%20>%200%20AND%20vote_num%20=%201000;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE vote_record \N index_merge PRIMARY,votenum,vote votenum,PRIMARY 8,4 \N 51 100.00 Using%20intersect(votenum,PRIMARY);%20Using%20where
用到了两个索引,votenum,PRIMARY。
这是为什么呢。
再看一个语句
EXPLAIN%20SELECT%20*%20FROM%20vote_record%20WHERE%20id%20=%201000%20AND%20vote_num%20>%201000
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE vote_record \N const PRIMARY,votenum PRIMARY 4 const 1 100.00 \N
也只有主键用到了索引。这是因为只有最左前缀索引可以用>或<,其他索引用<或者>会导致用不到索引。
下面是几个网上参考的例子:
一:索引是sql语句优化的关键,学会使用慢日志和执行计划分析sql
背景:使用A电脑安装mysql,B电脑通过xshell方式连接,数据内容我都已经创建好,现在我已正常的进入到mysql中
步骤1:设置慢查询日志的超时时间,先查看日志存放路径查询慢日志的地址,因为有慢查询的内容,就会到这个日志中:
show global variables like "%slow%";
![]()
2.开启慢查询日志
set global slow_query_log=on;
3.查看慢查询日志的设置时间,是否是自己需要的
show global variables like "%long%";

4.如果不是自己想的时间,修改慢查询时间,只要超过了以下的设置时间,查询的日志就会到刚刚的日志中,我设置查询时间超过1S就进入到慢查询日志中
set global long_query_time=1;
5.大数据已准备,进行数据的查询,xshell最好开两个窗口,一个查看日志,一个执行内容
Sql查询语句:select sql_no_cache * from employees_tmp where first_name='Duangkaew' and gender='M'

发现查数据的总时间去掉了17.74S
查看日志:打开日志


标记1:执行的sql语句
标记2:执行sql的时间,我的是10点52执行的
标记3:使用那台机器
标记4:执行时间,query_tims,查询数据的时间
标记5:不知道是干嘛的
标记6:执行耗时的sql语句,我在想我1的应该是截取错了!但是记住最后一定是显示耗时是因为执行什么sql造成的
6.执行打印计划,主要是查看是否使用了索引等其他内容,主要就是在sql前面加上explain 关键字
explain select sql_no_cache * from employees_tmp where first_name='Duangkaew' and gender='M';

描述extra中,表示只使用了where条件,没有其他什么索引之类的
7.进行sql优化,建一个fist_name的索引,索引就是将你需要的数据先给筛选出来,这样就可以节省很多扫描时间
create index firstname on employees_tmp(first_name);

注:创建索引时会很慢,是对整个表做了一个复制功能,并进行数据的一些分类(我猜是这样,所以会很慢)
8.查看建立的索引
show index from employees_tmp;

9.在执行查询语句,查看语句的执行时间
select sql_no_cache * from employees_tmp where first_name='Duangkaew' and gender='M'

发现时间已经有所提升了,其实选择索引也不一开始就知道,我们在试试使用性别,gender进行索引
10.删除已经有的索引,删除索引:
drop index first_name on employees_tmp;
11.创建性别的索引(性别是不怎么好的索引方式,因为有很多重复数据)
create index index_gendar on employees_tmp(gender);
在执行sql语句查询数据,查看查询执行时间,没有创建比较优秀的索引,导致查询时间还变长了,
为嘛还变长了,这个我没有弄懂

12.我们在试试使用创建组合索引,使用性别和姓名
alter table employees_tmp add index idx_union (first_name,gender);
在执行sql查看sql数据的执行时间
select sql_no_cache * from employees_tmp where first_name='Duangkaew' and gender='M'
速度提升了N多倍啊

查看创建的索引
show index from employees_tmp;

索引建的好真的一个好帮手,建不好就是费时的一个操作
目前还不知道为什么建立性别的索引会这么慢
二:sql优化注意要点,比如索引是否用到,查询优化是否改变了执行计划,以及一些细节
场景
我用的数据库是mysql5.6,下面简单的介绍下场景
课程表
create table Course(
c_id int PRIMARY KEY,name varchar(10)
)
数据100条
学生表:
create table Student(id int PRIMARY KEY,name varchar(10)
)
数据70000条
学生成绩表SC
CREATE table SC(
sc_id int PRIMARY KEY,
s_id int,
c_id int,
score int)
数据70w条
查询目的:
查找语文考100分的考生
查询语句:
select s.* from Student s where s.s_id in (select s_id from SC sc where sc.c_id = 0 and sc.score = 100 )
执行时间:30248.271s
晕,为什么这么慢,先来查看下查询计划:
EXPLAIN select s.* from Student s where s.s_id in (select s_id from SC sc where sc.c_id = 0 and sc.score = 100 )

发现没有用到索引,type全是ALL,那么首先想到的就是建立一个索引,建立索引的字段当然是在where条件的字段。
先给sc表的c_id和score建个索引
CREATE index sc_c_id_index on SC(c_id);
CREATE index sc_score_index on SC(score);
再次执行上述查询语句,时间为: 1.054s
快了3w多倍,大大缩短了查询时间,看来索引能极大程度的提高查询效率,看来建索引很有必要,很多时候都忘记建
索引了,数据量小的的时候压根没感觉,这优化感觉挺爽。
但是1s的时间还是太长了,还能进行优化吗,仔细看执行计划:

查看优化后的sql:
SELECT
`YSB`.`s`.`s_id` AS `s_id`, `YSB`.`s`.`name` AS `name`FROM
`YSB`.`Student` `s`WHERE
< in_optimizer > ( `YSB`.`s`.`s_id` ,< EXISTS > ( SELECT
1
FROM
`YSB`.`SC` `sc`
WHERE
(
(`YSB`.`sc`.`c_id` = 0) AND (`YSB`.`sc`.`score` = 100) AND (
< CACHE > (`YSB`.`s`.`s_id`) = `YSB`.`sc`.`s_id`
)
)
)
)
补充:这里有网友问怎么查看优化后的语句
方法如下:
在命令窗口执行


有type=all
按照我之前的想法,该sql的执行的顺序应该是先执行子查询
select s_id from SC sc where sc.c_id = 0 and sc.score = 100
耗时:0.001s
得到如下结果:

然后再执行
select s.* from Student s where s.s_id in(7,29,5000)
耗时:0.001s
这样就是相当快了啊,Mysql竟然不是先执行里层的查询,而是将sql优化成了exists子句,并出现了EPENDENT SUBQUERY,
mysql是先执行外层查询,再执行里层的查询,这样就要循环70007*11=770077次。
那么改用连接查询呢?
SELECT s.* from Student sINNER JOIN SC scon sc.s_id = s.s_idwhere sc.c_id=0 and sc.score=100
这里为了重新分析连接查询的情况,先暂时删除索引sc_c_id_index,sc_score_index
执行时间是:0.057s
效率有所提高,看看执行计划:

这里有连表的情况出现,我猜想是不是要给sc表的s_id建立个索引
CREATE index sc_s_id_index on SC(s_id);
show index from SC

在执行连接查询
时间: 1.076s,竟然时间还变长了,什么原因?查看执行计划:

优化后的查询语句为:
SELECT
`YSB`.`s`.`s_id` AS `s_id`, `YSB`.`s`.`name` AS `name`FROM
`YSB`.`Student` `s`JOIN `YSB`.`SC` `sc`WHERE
(
( `YSB`.`sc`.`s_id` = `YSB`.`s`.`s_id`
) AND (`YSB`.`sc`.`score` = 100) AND (`YSB`.`sc`.`c_id` = 0)
)
貌似是先做的连接查询,再执行的where过滤
回到前面的执行计划:

这里是先做的where过滤,再做连表,执行计划还不是固定的,那么我们先看下标准的sql执行顺序:

正常情况下是先join再where过滤,但是我们这里的情况,如果先join,将会有70w条数据发送join做操,因此先执行where
过滤是明智方案,现在为了排除mysql的查询优化,我自己写一条优化后的sql
SELECT
s.*FROM
( SELECT
* FROM
SC sc WHERE
sc.c_id = 0
AND sc.score = 100
) tINNER JOIN Student s ON t.s_id = s.s_id
即先执行sc表的过滤,再进行表连接,执行时间为:0.054s
和之前没有建s_id索引的时间差不多
查看执行计划:

先提取sc再连表,这样效率就高多了,现在的问题是提取sc的时候出现了扫描表,那么现在可以明确需要建立相关索引
CREATE index sc_c_id_index on SC(c_id);
CREATE index sc_score_index on SC(score);
再执行查询:
SELECT
s.*FROM
( SELECT
* FROM
SC sc WHERE
sc.c_id = 0
AND sc.score = 100
) tINNER JOIN Student s ON t.s_id = s.s_id
执行时间为:0.001s,这个时间相当靠谱,快了50倍
执行计划:

我们会看到,先提取sc,再连表,都用到了索引。
那么再来执行下sql
SELECT s.* from Student sINNER JOIN SC scon sc.s_id = s.s_idwhere sc.c_id=0 and sc.score=100
执行时间0.001s
执行计划:

这里是mysql进行了查询语句优化,先执行了where过滤,再执行连接操作,且都用到了索引。
总结:
1.mysql嵌套子查询效率确实比较低
2.可以将其优化成连接查询
3.建立合适的索引
4.学会分析sql执行计划,mysql会对sql进行优化,所以分析执行计划很重要
由于时间问题,这篇文章先写到这里,后续再分享其他的sql优化经历。
三、海量数据分页查找时如何使用主键索引进行优化
mysql百万级分页优化
普通分页
数据分页在网页中十分多见,分页一般都是limit start,offset,然后根据页码page计算start
select * from user limit 1,20
这种分页在几十万的时候分页效率就会比较低了,MySQL需要从头开始一直往后计算,这样大大影响效率
SELECT * from user limit 100001,20; //time 0.151s
explain SELECT * from user limit 100001,20;
我们可以用explain分析下语句,没有用到任何索引,MySQL执行的行数是16W+,于是我们可以想用到索引去实现分页

优化分页
使用主键索引来优化数据分页
select * from user where id>(select id from user where id>=100000 limit 1) limit 20; //time 0.003s
使用explain分析语句,MySQL这次扫描的行数是8W+,时间也大大缩短。
explain select * from user where id>(select id from user where id>=100000 limit 1) limit 20;

总结
在数据量比较大的时候,我们尽量去利用索引来优化语句。上面的优化方法如果id不是主键索引,查询效率比第一种还要低点。我们可以先使用explain来分析语句,查看语句的执行顺序和执行性能。