索引原理与慢查询优化
介绍
为何要有索引?
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。
什么是索引?
索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能
非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。
索引的介绍
数据库中专门用于帮助用户快速查找数据的一种数据结构。类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置吗,然后直接获取。
创建/删除索引的语法
#方法一:创建表时 CREATE TABLE 表名 ( 字段名1 数据类型 [完整性约束条件…], 字段名2 数据类型 [完整性约束条件…], [UNIQUE | FULLTEXT | SPATIAL ] INDEX | KEY [索引名] (字段名[(长度)] [ASC |DESC]) ); #方法二:CREATE在已存在的表上创建索引 CREATE [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名 ON 表名 (字段名[(长度)] [ASC |DESC]) ; #方法三:ALTER TABLE在已存在的表上创建索引 ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名 (字段名[(长度)] [ASC |DESC]) ; #删除索引:DROP INDEX 索引名 ON 表名字;
普通索引:
作用:仅有一个加速查找
在创建表的时候就创建索引:
第一种:
create table db6.t1( -> id int auto_increment not null primary key, -> name char(10), -> sex char(10), -> age int default 6, -> index ix_age(name) -> ) engine = innodb default charset = utf8; 在数据库db6中创建一个t1表并且为t1表中的那么列设定索引
create table db6.t1( -> id int auto_increment not null primary key, -> name char(10), -> sex char(10), -> age int default 6, -> index ix_age(name) -> ) engine = innodb default charset = utf8; 创建索引 name
create index 索引的名字 on 表名(列名)
drop index 索引的名字 on 表名
show index from 表名
唯一索引
唯一索引有两个功能:加速查找和唯一约束(可含null)
create table userinfo( id int not null auto_increment primary key, name varchar(32) not null, email varchar(64) not null, unique index ix_name(name) );
对userinfo表中的name列创建一个唯一索引 ix_name
create unique index 索引名 on 表名(列名)
drop index 索引名 on 表名
主键索引
主键索引有两个功能: 加速查找和唯一约束(不含null)
create table userinfo( id int not null auto_increment primary key, name varchar(32) not null, email varchar(64) not null, unique index ix_name(name) ) or create table userinfo( id int not null auto_increment, name varchar(32) not null, email varchar(64) not null, primary key(id), unique index ix_name(name) ) 这是给你的userinfo表中的id字段设置为主键 或者你先创建后面再添加主键也可以
alter table 表名 add primary key(列名);
alter table 表名 drop primary key; alter table 表名 modify 列名 int, drop primary key;
组合索引
组合索引是将n个列组合成一个索引
其应用场景为:频繁的同时使用n列来进行查询,如:where name = '老王' and email = 'laowang@qq.com'。
create index 索引名 on 表名(列名1,列名2);
索引名词
#覆盖索引:在索引文件中直接获取数据 例如: select name from userinfo where name = 'laowang30000';
当你的表中name列设定为索引后 那么你再通过索引来查找name列的信息就叫 覆盖索引 #索引合并:把多个单列索引合并成使用 例如: select * from userinfo where name = 'alex13131' and id = 13131;
索引合并是你设定表中的两个列的索引后要查找的判断信息为这两个列的条件就是所以合并
正确使用索引
数据库表中添加索引后确实会让查询速度起飞,但前提必须是正确的使用索引来查询,如果以错误的方式使用,则即使建立索引也会不奏效。
使用索引,我们必须知道:
(1)创建索引
(2)命中索引
(3)正确使用索引
准备:
#1. 准备表 create table userinfo( id int, name varchar(20), gender char(6), email varchar(50) ); #2. 创建存储过程,实现批量插入记录 delimiter $$ #声明存储过程的结束符号为$$ create procedure auto_insert1() BEGIN declare i int default 1; while(i<3000000)do insert into userinfo values(i,concat('alex',i),'male',concat('egon',i,'@oldboy')); set i=i+1; end while; END$$ #$$结束 delimiter ; #重新声明分号为结束符号 #3. 查看存储过程 show create procedure auto_insert1\G #4. 调用存储过程 call auto_insert1();
测试:
- like '%xx' select * from userinfo where name like '%al'; #没有显示 - 使用函数 select * from userinfo where reverse(name) = 'alex333'; #这个也没有显示 因为没有正中索引 - or select * from userinfo where id = 1 or email = 'alex122@oldbody'; 这个只有一条显示
特别的:当or条件中有未建立索引的列才失效,以下会走索引 select * from userinfo where id = 1 or name = 'alex1222'; #会显示两条 因为你已经设置为name为索引 所以两个都可以找到
select * from userinfo where id = 1 or email = 'alex122@oldbody' and name = 'alex112'
也就是你的判断语句如果包含or那么你的列如果有没有建立索引的那么只显示第一个最左边的 因为有最左前缀匹配
什么是最左前缀呢?
最左前缀匹配: create index ix_name_email on userinfo(name,email); select * from userinfo where name = 'alex'; select * from userinfo where name = 'alex' and email='alex@oldBody'; select * from userinfo where email='alex@oldBody'; 如果使用组合索引如上,name和email组合索引之后,查询 (1)name和email ---使用索引 (2)name ---使用索引 (3)email ---没有使用索引 对于同时搜索n个条件时,组合索引的性能好于多个单列索引
******组合索引的性能>索引合并的性能*********
索引的注意事项
(1)避免使用select * (2)count(1)或count(列) 代替count(*) (3)创建表时尽量使用char代替varchar (4)表的字段顺序固定长度的字段优先 (5)组合索引代替多个单列索引(经常使用多个条件查询时) (6)尽量使用短索引 (create index ix_title on tb(title(16));特殊的数据类型 text类型) (7)使用连接(join)来代替子查询 (8)连表时注意条件类型需一致 (9)索引散列(重复少)不适用于建索引,例如:性别不合适
查询优化神器-explain
关于explain命令相信大家并不陌生,具体用法和字段含义可以参考官网explain-output,这里需要强调rows是核心指标,绝大部分rows小的语句执行一定很快(有例外,下面会讲到)。所以优化语句基本上都是在优化rows。
执行计划
explain + 查询SQL - 用于显示SQL执行信息参数,根据参考信息可以进行SQL优化
explain select * from t1;
这种查找全局的速度慢类型是all
explain select * from userinfo where name = 'alex122';
这种依靠普通索引的查找比上面查找全局的要快 类型是ref的
EXPLAIN SELECT * FROM
(
select id,name from userinfo where id < 20
) as A
;
这个也是查找前20个的id和name所以这个是type是all速度也慢
执行顺序:
执行计划:让mysql预估执行操作(一般正确) all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const id,email 慢: select * from userinfo3 where name='alex' explain select * from userinfo3 where name='alex' type: ALL(全表扫描) select * from userinfo3 limit 1; 快: select * from userinfo3 where email='alex' type: const(走索引)
慢日志记录
开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。
(1) 进入MySql 查询是否开了慢查询 show variables like 'slow_query%'; 参数解释: slow_query_log 慢查询开启状态 OFF 未开启 ON 为开启 slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录)
(2)查看慢查询超时时间 show variables like 'long%'; ong_query_time 查询超过多少秒才记录 默认10秒 (3)开启慢日志(1)(是否开启慢查询日志,1表示开启,0表示关闭。) set global slow_query_log=1; (4)再次查看 show variables like '%slow_query_log%'; (5)开启慢日志(2):(推荐) 在my.cnf 文件中 找到[mysqld]下面添加: slow_query_log =1 slow_query_log_file=C:\mysql-5.6.40-winx64\data\localhost-slow.log long_query_time = 1 参数说明: slow_query_log 慢查询开启状态 1 为开启 slow_query_log_file 慢查询日志存放的位置 long_query_time 查询超过多少秒才记录 默认10秒 修改为1秒
分页性能相关方案
先回顾一下,如何取当前表中的前10条记录,每十条取一次.......
第1页: select * from userinfo limit 0,10; 第2页: select * from userinfo limit 10,10; 第3页: select * from userinfo limit 20,10; 第4页: select * from userinfo limit 30,10; ...... 第2000010页 select * from userinfo limit 2000000,10; PS:会发现,越往后查询,需要的时间约长,是因为越往后查,全文扫描查询,会去数据表中扫描查询。
最优的解决方案
(1)只有上一页和下一页 做一个记录:记录当前页的最大id或最小id 下一页: select * from userinfo where id>max_id limit 10; 上一页: select * from userinfo where id<min_id order by id desc limit 10; (2) 中间有页码的情况 select * from userinfo where id in( select id from (select * from userinfo where id > pre_max_id limit (cur_max_id-pre_max_id)*10) as A order by A.id desc limit 10 );
示例:
下一页: select * from userinfo where id > 3000 limit 10; 这样就是设置你的id大于你上一次查询的最大的值 然后就可以得到 这样效率也很高 上一页: select * from userinfo where id < 30000 order by id desc limit 10 ; 这个设置id小于你这一次的最小的id然后找到后再按照反序排列取前10个就得到了想要的值