一、介绍
1、什么是索引
索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针
通俗的讲,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。在没有索引的情况下,数据库会按照顺序逐条遍历记录,直至找到需要的数据为止。而有了相应的索引之后,数据库会直接在索引中查找符合条件的选项,直接在索引中定位需要的数据。
通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
2、索引的原理
索引一般以文件形式存在磁盘中(也可以存于内存中),存储的索引的原理大致概括为以空间换时间,数据库在未添加索引的时候进行查询默认的是进行全量搜索,也就是进行全局扫描,有多少条数据就要进行多少次查询,然后找到相匹配的数据就把他放到结果集中,直到全表扫描完。而建立索引之后,会将建立索引的KEY值放在一个n叉树上(BTree)。因为B树的特点就是适合在磁盘等直接存储设备上组织动态查找表,每次以索引进行条件查询时,会去树上根据key值直接进行搜索。
3、索引的优点
建立索引的目的是加快对表中记录的查找或排序
① 建立索引的列可以保证行的唯一性,生成唯一的rowId
② 建立索引可以有效缩短数据的检索时间
③ 建立索引可以加快表与表之间的连接
④ 为用来排序或者是分组的字段添加索引可以加快分组和排序顺序
4、索引的缺点
① 创建索引和维护索引需要时间成本,这个成本随着数据量的增加而加大
② 创建索引和维护索引需要空间成本,每一条索引都要占据数据库的物理存储空间,数据量越大,占用空间也越大(数据表占据的是数据库的数据空间)
③ 会降低表的增删改的效率,因为每次增删改索引需要进行动态维护,导致时间变长
二、聚簇索引与非聚簇索引
1、聚簇索引
InnoDB将通过主键聚集数据,如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引,一个表只能有一个聚簇索引
聚簇索引可以说是一种索引,也可以说是一种数据存储的方式。它的索引号和对应的记录是存在一起的。存储数据的顺序和索引顺序一致
优势:当主键为自增时,做按主键的范围查询
缺点:主键如果时uuid,无法保证顺序,做范围条件查询时,开销很大
2、非聚簇索引
非聚簇索引的叶子节点仍然是索引节点,只有有指向对应数据块的指针
一张表可以最多建249个非聚簇索引,建索引需要额外的内存。索引不是越多越好。每次往表里插入数据时,要同步更新索引。所以建索引是要慎重考虑
非聚簇索引中叶子节点的记录中需要保存主键,如需访问记录中其他部分还需要通过主键回表查询。即两次索引查找。有人疑问非聚簇索引中为什么不保存记录项的物理地址呢,当然可以记录物理地址,但是主键索引更新操作带来的索引分裂合并会改变其物理地址,这样索引的维护代价比较大,而即使回表查询,主键查找速度一般较快,影响不大。另外也可以通过覆盖索引【即索引项覆盖了select中的项】避免回表查询
三、索引的数据结构分类
mysql默认存储引擎innodb只显式支持B树索引,对于频繁访问的表,innodb会透明建立自适应hash索引, 即在B树索引基础上建立hash索引,可以显著提高查找效率,对于客户端是透明的,不可控制的,隐式的。支持范围查询,前缀匹配查询,等值查询,可以避免排序
1、B+TREE
索引用的数据结构是B+TREE,B是balance
① 普通索引
② 唯一性索引:普通索引+字段取值必须唯一
③ 主键自带索引
④ 联合索引(多列)
2、HASH索引
① 通过hash函数将键值直接映射为物理存储地址,使时间复杂度降低到O(1),本身存储是无序的,所以不能通过hash索引避免排序
② 很快、占内存,需要算hash值。只支持包括 “=” “in “在内的等值查询,不支持范围、前缀匹配查询
3、Mysql采用B+TREE索引的原因
B-树和B+树的区别在于B+树所有键值全部保存在叶子节点,而B-树则不然,B-树的键值根据树的结构分布在整个树上
① 遍历方便。B+树可以将键值保存在(线性表【数组或链表】)中,遍历线性表比索引树要快,因为保存在线性表中数据存储更加密集,B-Tree分散的 存储会导致更多的随机I/O,对于磁盘访问,随机I/O是比顺序I/O慢很多的,因为随机I/O需要额外的磁头寻道操作。顺序I/O有效减少寻道的次数
② 插入更新索引树时可以避免移动节点
③ 遍历任何节点的时间复杂度相同,即访问路径总是从根节点到叶子节点.相比B-树,访问时间略长.所以某些高频访问的搜索采用B-树,即访问频率越高 使其距离根节点越近
④ 范围查找方便。对于[A,B]区间的范围查找,B-树索引可以直接找到A,B对应的线性表中节点,只需要返回区间的所有节点 即为目标结果。而B-树则稍显麻烦需要继续遍历索引树
四、Mysql索引管理
1、MySQL的索引分类
① 普通索引:index:加速查找
② 唯一索引:unique:加速查找+约束(唯一)
③ 主键索引:primary key :加速查找+约束(不为空且唯一)
④ 联合索引
-primary key(id,name):联合主键索引
-unique(id,name):联合唯一索引
-index(id,name):联合普通索引
⑤ 全文索引fulltext:用于搜索很长一篇文章的时候,效果最好
2、索引创建的语句
CREATE TABLE table_name[col_name data type][unique|fulltext][index|key][index_name](col_name[length])[asc|desc]
● unique|fulltext为可选参数,分别表示唯一索引、全文索引
● index和key为同义词,两者作用相同,用来指定创建索引
● col_name为需要创建索引的字段列,该列必须从数据表中该定义的多个列中选择
● index_name指定索引的名称,为可选参数,如果不指定,默认col_name为索引值
● length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度
● asc或desc指定升序或降序的索引值存储
3、索引的创建
(1)普通索引(单列索引):
单列索引是最基本的索引,它没有任何限制。
① 直接创建索引:CREATE INDEX index_name ON table_name(col_name);
② 修改表结构的方式添加索引:ALTER TABLE table_name ADD INDEX index_name(col_name);
③ 创建表的时候同时创建索引
CREATETABLE表名 (
字段名1 数据类型 [完整性约束条件…],
字段名2 数据类型 [完整性约束条件…],
[UNIQUE| FULLTEXT | SPATIAL ] INDEX| KEY
[索引名] (字段名[(长度)] [ASC|DESC])
);
CREATETABLE`news` (
`id` int(11) NOTNULLAUTO_INCREMENT ,
`title` varchar(255) NOTNULL,
`content` varchar(255) NULL,
`time` varchar(20) NULLDEFAULTNULL,
PRIMARYKEY(`id`),
INDEXindex_name (title(255))
)
(2)复合索引(组合索引)
复合索引是在多个字段上创建的索引。复合索引遵守“最左前缀”原则,即在查询条件中使用了复合索引的第一个字段,索引才会被使用。因此,在复合索引中索引列的顺序至关重要。
① 创建复合索引:create index index_name on table_name(col_name1,col_name2,…);
② 修改表结构的方式添加索引:alter table table_name add index index_name(col_name,col_name2,…);
(3)唯一索引
唯一索引和普通索引类似,主要的区别在于,唯一索引限制列的值必须唯一,但允许存在空值(只允许存在一条空值)
如果在已经有数据的表上添加唯一性索引的话:
- 如果添加索引的列的值存在两个或者两个以上的空值,则不能创建唯一性索引会失败。(一般在创建表的时候,要对自动设置唯一性索引,需要在字段上加上 not null)
- 如果添加索引的列的值存在两个或者两个以上的null值,还是可以创建唯一性索引,只是后面创建的数据不能再插入null值 ,并且严格意义上此列并不是唯一的,因为存在多个null值。
对于多个字段创建唯一索引规定列值的组合必须唯一。
比如:在order表创建orderId字段和 productId字段 的唯一性索引,那么这两列的组合值必须唯一!
“空值” 和”NULL”的概念:
1:空值是不占用空间的
2:MySQL中的NULL其实是占用空间的
长度验证:注意空值的之间是没有空格的。
> select length(''),length(null),length(' ');
+------------+--------------+-------------+
| length('') | length(null) | length(' ') |
+------------+--------------+-------------+
| 0 | NULL | 1 |
+------------+--------------+-------------+
① 创建唯一索引
# 创建单个索引
CREATE UNIQUE INDEX index_name ON table_name(col_name);
# 创建多个索引
CREATE UNIQUE INDEX index_name on table_name(col_name,...);
② 修改表结构
# 单个
ALTER TABLE table_name ADD UNIQUE index index_name(col_name);
# 多个
ALTER TABLE table_name ADD UNIQUE index index_name(col_name,...);
③ 创建表的时候直接指定索引
CREATE TABLE `news` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` varchar(255) NOT NULL ,
`content` varchar(255) NULL ,
`time` varchar(20) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
UNIQUE index_name_unique(title)
)
(4)主键索引
主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引
① 主键索引(创建表时添加)
CREATE TABLE `news` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` varchar(255) NOT NULL ,
`content` varchar(255) NULL ,
`time` varchar(20) NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
② 主键索引(创建表后添加)
alter table tbl_name add primary key(col_name);
CREATE TABLE `order` (
`orderId` varchar(36) NOT NULL,
`productId` varchar(36) NOT NULL ,
`time` varchar(20) NULL DEFAULT NULL
)
alter table `order` add primary key(`orderId`);
(5)全文索引
在一般情况下,模糊查询都是通过 like 的方式进行查询。但是,对于海量数据,这并不是一个好办法,在 like “value%” 可以使用索引,但是对于 like “%value%” 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,但是对于海量数据,全表扫描是非常可怕的事情,所以 like 进行模糊匹配性能很差。
这种情况下,需要考虑使用全文搜索的方式进行优化。全文搜索在 MySQL 中是一个 FULLTEXT 类型索引。FULLTEXT 索引在 MySQL 5.6 版本之后支持 InnoDB,而之前的版本只支持 MyISAM 表。
全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。目前只有char、varchar,text 列上可以创建全文索引。
小技巧:在数据量较大时候,先将数据放入一个没有全局索引的表中,然后再用CREATE index创建fulltext索引,要比先为一张表建立fulltext然后再将数据写入的速度快很多。
① 创建表的适合添加全文索引
CREATE TABLE `news` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` varchar(255) NOT NULL ,
`content` text NOT NULL ,
`time` varchar(20) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
FULLTEXT (content)
)
② 修改表结构添加全文索引:
ALTER TABLE table_name ADD FULLTEXT index_fulltext_content(col_name)
③ 直接创建索引:
CREATE FULLTEXT INDEX index_fulltext_content ON table_name(col_name)
4、索引的查询和删除
索引的查看:show indexes from 表名
;
或者
show keys from 表名
;
索引的删除:
DROP INDEX index_name ON table_name;
或者
alter table 表名
drop index 索引名;
五、正确使用索引
1、覆盖索引
分析
select * from s1 where id=123;
该sql命中了索引,但未覆盖索引。
利用id=123到索引的数据结构中定位到该id在硬盘中的位置,或者说再数据表中的位置。
但是我们select的字段为*,除了id以外还需要其他字段,这就意味着,我们通过索引结构取到id还不够,
还需要利用该id再去找到该id所在行的其他字段值,这是需要时间的,很明显,如果我们只select id,
就减去了这份苦恼,如下
select id from s1 where id=123;
这条就是覆盖索引了,命中索引,且从索引的数据结构直接就取到了id在硬盘的地址,速度很快
2、联合索引
3、索引合并
#索引合并:把多个单列索引合并使用
#分析:
组合索引能做到的事情,我们都可以用索引合并去解决,比如
create index ne on s1(name,email);#组合索引
我们完全可以单独为name和email创建索引
组合索引可以命中:
select * from s1 where name='egon' ;
select * from s1 where name='egon' and email='adf';
索引合并可以命中:
select * from s1 where name='egon' ;
select * from s1 where email='adf';
select * from s1 where name='egon' and email='adf';
乍一看好像索引合并更好了:可以命中更多的情况,但其实要分情况去看,如果是name='egon' and email='adf',
那么组合索引的效率要高于索引合并,如果是单条件查,那么还是用索引合并比较合理
六、使用索引应注意的规则
1、查看索引的使用情况:
show status like ‘Handler_read%’;
handler_read_key:这个值越高越好,越高表示使用索引查询到的次数
handler_read_rnd_next:这个值越高,说明查询低效
2、使用索引时,有以下一些技巧和注意事项:
(1) 越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快。
(2) 简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较更复杂。在MySQL中,应该用内置的日期和时间数据类型,而不是用字符串来存储时间;以及用整型数据类型存储IP地址。
(3) 尽量避免NULL:应该指定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值。
(4) 索引不会包含有NULL值的列。
注:如果是同样的sql如果在之前能够使用到索引,那么现在使用不到索引,以下几种主要情况:
① 随着表的增长,where条件出来的数据太多,大于15%,使得索引失效(会导致CBO计算走索引花费大于走全表)
② 统计信息失效:需要重新搜集统计信息
③ 索引本身失效:需要重建索引
#1.最左前缀匹配原则,非常重要的原则,
create index ix_name_email on s1(name,email,)
- 最左前缀匹配:必须按照从左到右的顺序匹配
select * from s1 where name='egon'; #可以
select * from s1 where name='egon' and email='asdf'; #可以
select * from s1 where email='alex@oldboy.com'; #不可以
mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,
比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,
d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
#2.=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器
会帮你优化成索引可以识别的形式
#3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),
表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、
性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,
这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录
#4.索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’
就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,
但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。
所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
3、常见索引失效的情况:使用explain查看索引是否生效
创建一个students表:
其中stud_id为主键!
DROP TABLE IF EXISTS `students`;
CREATE TABLE `students` (
`stud_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
`phone` varchar(1) NOT NULL,
`create_date` date DEFAULT NULL,
PRIMARY KEY (`stud_id`)
)
INSERT INTO `learn_mybatis`.`students` (`stud_id`, `name`, `email`, `phone`, `create_date`)
VALUES ('1', 'admin', 'student1@gmail.com', '18729902095', '1983-06-25');
INSERT INTO `learn_mybatis`.`students` (`stud_id`, `name`, `email`, `phone`, `create_date`)
VALUES ('2', 'root', '74298110186@qq.com', '2', '1983-12-25');
INSERT INTO `learn_mybatis`.`students` (`stud_id`, `name`, `email`, `phone`, `create_date`)
VALUES ('3', '110', '7429811086@qq.com', '3dsad', '2017-04-28');
① 在where后使用or,导致索引失效(尽量少用or)
简单实例演示:
创建两个普通索引,
CREATE INDEX index_name_email ON students(email);
CREATE INDEX index_name_phone ON students(phone);
使用下面查询sql,
# 使用了索引
EXPLAIN select * from students where stud_id='1' or phone='18729902095'
# 使用了索引
EXPLAIN select * from students where stud_id='1' or email='742981086@qq.com'
#--------------------------
# 没有使用索引
EXPLAIN select * from students where phone='18729902095' or email='742981086@qq.com'
# 没有使用索引
EXPLAIN select * from students where stud_id='1' or phone='222' or email='742981086@qq.com'
② 使用like ,like查询是以%开头
在1的基础上,还是使用 index_name_email 索引。
使用下面查询sql
# 使用了index_name_email索引
EXPLAIN select * from students where email like '742981086@qq.com%'
# 没有使用index_name_email索引,索引失效
EXPLAIN select * from students where email like '%742981086@qq.com'
# 没有使用index_name_email索引,索引失效
EXPLAIN select * from students where email like '%742981086@qq.com%'
③ 复合索引遵守“最左前缀”原则,即在查询条件中使用了复合索引的第一个字段,索引才会被使用
删除1的基础创建的 index_name_email 和 index_name_phone 索引。
重新创建一个复合索引:
create index index_email_phone on students(email,phone);
使用下面查询sql
# 使用了 index_email_phone 索引
EXPLAIN select * from students where email='742981086@qq.com' and phone='18729902095'
# 使用了 index_email_phone 索引
EXPLAIN select * from students where phone='18729902095' and email='742981086@qq.com'
# 使用了 index_email_phone 索引
EXPLAIN select * from students where email='742981086@qq.com' and name='admin'
# 没有使用index_email_phone索引,复合索引失效
EXPLAIN select * from students where phone='18729902095' and name='admin'
④ 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
给name创建一个索引!
CREATE INDEX index_name ON students(name);
# 使用索引
EXPLAIN select * from students where name='110'
# 没有使用索引
EXPLAIN select * from students where name=110
⑤ 使用in导致索引失效
# 使用索引
EXPLAIN select * from students where name='admin'
# 没有使用索引
EXPLAIN SELECT * from students where name in ('admin')
⑥ DATE_FORMAT()格式化时间,格式化后的时间再去比较,可能会导致索引失效。
删除 students 上的创建的索引!重新在create_date创建一个索引!
CREATE INDEX index_create_date ON students(create_date);
# 使用索引
EXPLAIN SELECT * from students where create_date >= '2010-05-05'
# 没有使用索引
EXPLAIN SELECT * from students where DATE_FORMAT(create_date,'%Y-%m-%d') >= '2010-05-05'
⑦ 对于order by、group by 、 union、 distinc 中的字段出现在where条件中时,才会利用索引!
七、应用索引进行查询
1、索引的应用
2、利用索引进行排序
① MySQL中,有两种方式生成有序结果集:一是使用filesort(慢查询),二是按索引顺序扫描
② 当索引的顺序与order by中的列顺序相同且所有的列是同一方向(全部升序或全部降序)时,可以使用索引来排序
③ 如果查询是连接多个表,仅当order by中的所有列都是第一个表的列时才会使用索引,其他情况都会使用filesort。
④ 当查询排序次数非常多,建议不在MySQL中做排序,可以放到Java中
3、数据准备:
4、检测是否使用索引:
5、注:不是索引越多越好
八、索引列选择合适的数据类型
1、越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快 VARCHAR(1000) INT(10) ✔
2、简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较复杂。IP的存储问题
3、主键整型最好
4、尽量避免NULL,含有NULL的列做统计和查询优化很麻烦
九、一般原则
1、有大量重复值、经常进行范围查询(=、>、<、>=、<=、between、in)和order by、group by发生的列,可考虑建立聚簇索引。主键的范围查询有走索引,非主键的范围查询不走索引
2、经常同时存取多列,且每列都含有重复值可考虑建立组合索引,其前导列一定是使用最频繁的列
3、索引不会包含有NULL值的列。只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以在数据库设计时不要让字段的默认值为NULL
4、尽量使用短索引。对字符串列进行索引,如果可能应该指定一个前缀长度。例如有一个CHAR(255)的列,如果在前10个或20个字符内,多数值时惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度,而且可以节省磁盘空间和I/O操作
5、索引列排序。MYSQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引
6、like语句操作。一般情况下不鼓励使用like操作,如果非使用不可,如果使用也是一个问题。like “%aaa%”不会使用索引,而like “aaa%”可以使用索引
7、尽量不要在列上进行运算。如select from users where YEAR(adddate) < 2007,将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此可以改成:select from users where adddate < ‘2007-01-01’
十、虽然建了索引,但是不会走索引的情况
1、存在NULL值
2、NOT条件:包括<>、NOT、IN、not exists
3、like通配符的后匹配
4、函数运算
select from test where upper(name) = ‘ZHANGSHAN’
可以改为:select from test where name =upper( ‘ZHANGSHAN’)
5、数据类型的转换:当查询条件存在隐式转换时,索引会失效,比如在数据库里id村的int类型,但在查询时,却用了下面的形式:
select * from test where id = ‘123’
6、复合索引前导列区分不大,如性别,取值只有两个