数据库的索引
一、为什么要有索引?
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。
什么是索引?
索引在MySQL中也叫是一种“键”,是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能
非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查
二、索引的原理
本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
数据库也是一样,但显然要复杂的多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段......这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。
三、索引的数据结构
B+树
B+树是MySQL中的使用频繁的一种索引,InnoDB存储引擎就是用B+Tree实现其索引结构

特点:
1、非叶子节点的的磁盘存储4个键值(既表中的主键)及指针(存储子节点的地址信息)信息
2、叶子节点记录表中的除主键外的数据
分类
B+Tree索引可以分为聚集索引和辅助索引
1、聚集索引:
按照每张表的主键构造一颗B+树,叶子结(数据页)存放的即为整张表的行记录数据
#如果未定义主键,MySQL取第一个唯一索引(unique)而且只含非空列(NOT NULL)作为主键,InnoDB使用它作为聚簇索引。
#如果没有这样的列,InnoDB就自己产生一个这样的ID值,它有六个字节,而且是隐藏的,使其作为聚簇索引。
优点: 1、由于B+树是双向链表,查询速度非常的快
2、如果是范围查找的化如查找主件某一个范围内的数据,通过叶子节点的上层中间节点就可以得到页的范围
2、辅助索引:
表中除了聚集索引之外都是辅助索引,区别是辅助索引的叶子节点不包含记录的全部数据
每张表可以有多个辅助索引,但只能有一个聚集索引,当通过辅助索引来寻找数据时,
InnoDB存储引擎会遍历辅助索引并通过叶子级别的指针获得只想主键索引的主键,然后再通过主键索引来找到一个完整的行记录。
3、两者的区别
聚集索引与辅助索引相同的是:不管是聚集索引还是辅助索引,其内部都是B+树的形式,即高度是平衡的,叶子结点存放着所有的数据。
聚集索引与辅助索引不同的是:叶子结点存放的是否是一整行的信息
四、创建索引
在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE INDEX或ALTER TABLE来为表增加索引。
1.ALTER TABLE
ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。
ALTER TABLE table_name ADD INDEX index_name (column_list)
ALTER TABLE table_name ADD UNIQUE (column_list)
ALTER TABLE table_name ADD PRIMARY KEY (column_list)
其中table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。索引名index_name可选,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。
2.CREATE INDEX
CREATE INDEX可对表增加普通索引或UNIQUE索引。
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
table_name、index_name和column_list具有与ALTER TABLE语句中相同的含义,索引名可不选。另外,不能用CREATE INDEX语句创建PRIMARY KEY索引。
1、普通索引,
作用:仅有一个加速查找
1 create table userinfo( 2 nid int not null auto_increment primary key, 3 name varchar(32) not null, 4 email varchar(64) not null, 5 index ix_name(name) 6 );
2、唯一索引
唯一索引有两个功能:加速查找和唯一约束(可含null)
1 create table userinfo( 2 id int not null auto_increment primary key, 3 name varchar(32) not null, 4 email varchar(64) not null, 5 unique index ix_name(name) 6 );
create unique index 索引名 on 表名(列名)
drop index 索引名 on 表名;
3、主键索引
1 create table userinfo( 2 3 id int not null auto_increment primary key, 4 name varchar(32) not null, 5 email varchar(64) not null, 6 unique index ix_name(name) 7 ) 8 or 9 10 create table userinfo( 11 12 id int not null auto_increment, 13 name varchar(32) not null, 14 email varchar(64) not null, 15 primary key(nid), 16 unique index ix_name(name) 17 )
1 alter table 表名 add primary key(列名);
1 alter table 表名 drop primary key; 2 alter table 表名 modify 列名 int, drop primary key;
4、组合索引
组合索引是将n个列组合成一个索引
其应用场景为:频繁的同时使用n列来进行查询,如:where name = 'Tom' and email = 'Tom@qq.com'。
create index 索引名 on 表名(列名1,列名2);
五、开启慢日志
1 (1) 进入MySql 查询是否开了慢查询 2 show variables like 'slow_query%'; 3 参数解释: 4 slow_query_log 慢查询开启状态 OFF 未开启 ON 为开启 5 slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录) 6 7 (2)查看慢查询超时时间 8 show variables like 'long%'; 9 ong_query_time 查询超过多少秒才记录 默认10秒 10 11 (3)开启慢日志(1)(是否开启慢查询日志,1表示开启,0表示关闭。) 12 set global slow_query_log=1; 13 (4)再次查看 14 show variables like '%slow_query_log%'; 15 16 (5)开启慢日志(2):(推荐) 17 在my.cnf 文件中 18 找到[mysqld]下面添加: 19 slow_query_log =1 20 slow_query_log_file=C:\mysql-5.6.40-winx64\data\localhost-slow.log 21 long_query_time = 1 22 23 参数说明: 24 slow_query_log 慢查询开启状态 1 为开启 25 slow_query_log_file 慢查询日志存放的位置 26 long_query_time 查询超过多少秒才记录 默认10秒 修改为1秒
六、执行计划
explain + 查询SQL - 用于显示SQL执行信息参数,根据参考信息可以进行SQL优化
mysql> explain select * from userinfo; +----+-------------+----------+------+---------------+------+---------+------+---------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+------+---------+------+---------+-------+ | 1 | SIMPLE | userinfo | ALL | NULL | NULL | NULL | NULL | 2973016 | NULL | +----+-------------+----------+------+---------------+------+---------+------+---------+-------+ mysql> explain select * from (select id,name from userinfo where id <20) as A; +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 19 | NULL | | 2 | DERIVED | userinfo | range | PRIMARY | PRIMARY | 4 | NULL | 19 | Using where | +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ rows in set (0.05 sec)
参数说明
1 select_type: 2 查询类型 3 SIMPLE 简单查询 4 PRIMARY 最外层查询 5 SUBQUERY 映射为子查询 6 DERIVED 子查询 7 UNION 联合 8 UNION RESULT 使用联合的结果 9 table: 10 正在访问的表名 11 type: 12 查询时的访问方式,性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const 13 ALL 全表扫描,对于数据表从头到尾找一遍 14 select * from userinfo; 15 特别的:如果有limit限制,则找到之后就不在继续向下扫描 16 select * from userinfo where email = 'alex112@oldboy' 17 select * from userinfo where email = 'alex112@oldboy' limit 1; 18 虽然上述两个语句都会进行全表扫描,第二句使用了limit,则找到一个后就不再继续扫描。 19 20 INDEX : 全索引扫描,对索引从头到尾找一遍 21 select nid from userinfo; 22 23 RANGE: 对索引列进行范围查找 24 select * from userinfo where name < 'alex'; 25 PS: 26 between and 27 in 28 > >= < <= 操作 29 注意:!= 和 > 符号 30 31 32 INDEX_MERGE: 合并索引,使用多个单列索引搜索 33 select * from userinfo where name = 'alex' or nid in (11,22,33); 34 35 REF: 根据索引查找一个或多个值 36 select * from userinfo where name = 'alex112'; 37 38 EQ_REF: 连接时使用primary key 或 unique类型 39 select userinfo2.id,userinfo.name from userinfo2 left join tuserinfo on userinfo2.id = userinfo.id; 40 41 42 43 CONST:常量 44 表最多有一个匹配行,因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次。 45 select id from userinfo where id = 2 ; 46 47 SYSTEM:系统 48 表仅有一行(=系统表)。这是const联接类型的一个特例。 49 select * from (select id from userinfo where id = 1) as A; 50 51 52 possible_keys:可能使用的索引 53 54 key:真实使用的 55 56 key_len: MySQL中使用索引字节长度 57 58 rows: mysql估计为了找到所需的行而要读取的行数 ------ 只是预估值 59 60 extra: 61 该列包含MySQL解决查询的详细信息 62 “Using index” 63 此值表示mysql将使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了。 64 “Using where” 65 这意味着mysql服务器将在存储引擎检索行后再进行过滤,许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where子句的查询都会显示“Using where”。有时“Using where”的出现就是一个暗示:查询可受益于不同的索引。 66 “Using temporary” 67 这意味着mysql在对查询结果排序时会使用一个临时表。 68 “Using filesort” 69 这意味着mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成,explain不会告诉你mysql将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成。 70 “Range checked for each record(index map: N)” 71 这个意味着没有好用的索引,新的索引将在联接的每一行上重新估算,N是显示在possible_keys列中索引的位图,并且是冗余的
七、分页性能
1 第1页: 2 select * from userinfo limit 0,10; 3 第2页: 4 select * from userinfo limit 10,10; 5 第3页: 6 select * from userinfo limit 20,10; 7 第4页: 8 select * from userinfo limit 30,10; 9 ...... 10 第2000010页 11 select * from userinfo limit 2000000,10; 12 13 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 );

浙公网安备 33010602011771号