《高性能Mysql》-第五章-创建高性能的索引

2019-10-08  本文已影响0人  j4fan

1.b-树索引

索引首先要回顾一下b树b+树的特点和区别,数据库引擎用b+树的好处有查询时间比较稳定,b+树比较适合范围查询,b+树比较矮胖,b树的高度代表随机io的次数,相对的查询时间会比较短。

如图是索引的一般设计

mysql索引

非叶子也拥有指向叶子页的指针,叶子页之间也存在指针。

文中说道:逻辑页依赖于不同的存储引擎,对于InnoDB为16k。对此“页”的概念做一些资料查找。

页是innodb存储引擎的最小存储单位,有数据页,undo页,系统页,事务处理页。默认的页是16kb,每个页上至少有2条以上的记录。

b-树适合的查询类型

其中具体包含如下(索引为lastname,firstname,birthday)

orderBy是否能用到索引的条件和查询是否能用到索引的条件一致

不能使用索引的限制

2.哈希索引

哈希索引基于哈希表实现,只有精确匹配索引所有列才有效。对于每一行数据,对所有索引列计算哈希码,哈希索引将哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。

Memory引擎显示支持哈希索引,同时也支持B-tree索引。而且Memory引擎支持非唯一哈希索引,索引会以链表的方式存在同一条哈希条目中。

哈希索引速度非常快,但是限制如下:

innodb有个特殊功能叫"自适应哈希索引(adaptive hash index)",当Innodb注意到某些索引值使用非常频繁的时候,会在内存基于B-tree索引之上创建一个哈希索引,让B-Tree索引具有哈希索引的优点,这个功能是完全自动的,内部行为,用户无法控制或者配置。

这里有个扩展知识就是在平时使用Mysql时我们对一个字段添加了hash索引的话,重新show INDEXES from tableA 会发现的index_type还是BTREE,这个就是上面所说的自适应哈希,其实还是在BTREE的基础上做了哈希索引。

在InnoDB中如何优化如下查询
select id from url where url = "http://www.mysql.com";
可以在该数据库手动建立一个哈希索引列,基于这一列来查询会更快
select id from url where url = "http://www.mysql.com" and url_crc=CRC32("http://www.mysql.com")

这种方案麻烦的地方在于需要手动维护url_crc

全文索引

更类似于搜索引擎做的事情,不是简单的where匹配,全文索引适用于match against操作。

索引的优点

如何才能使用到索引

1.独立的列

索引列不能是表达式的一部分,也不能是函数的参数

select * from where To_DAYS(current_date) < 10

2.前缀索引和索引选择性

索引选择性就是说用了这个索引可以筛选多少行,可以过滤多少行,不重复的索引值越多,选择性越大。

文中的例子是对city这个地段做索引,可以用这个公式来测算索引选择性

select count(distinct city)/count(*) from city_demo

计算前缀索引长度的一个建议就是计算完整列的选择性,使得前缀的选择接近于完整列。

select count(distinct left(city,3))/count(*) as sel3,
count(distinct left(city,4))/count(*) as sel4 from city_demo

当索引字节增加,索引选择性没有太大变化时,即认为当前长度足够了。

alter table city_demo add key (city(7));

3.多列索引

4.B—tree索引选择合适的索引列顺序

确定索引列的顺序的其中一个原则是将选择性最高的列放在最前面。

以下面查询为例

select * from payment where staff_id = 2 and customer_id = 584;

是应该创建(staff_id,customer_id)的索引还是颠倒下顺序。
实际上如果staff_id和customer_id的组合数接近总行数,那就说明数据没什么区分度

select count(distinct staff_id,customer_id)/count(*) 比值非常小,说明staff_id,customer_id的组合重复非常多的时候

那么这个联合索引就没啥用,反而如果customer_id的区分度很高,只用customer_id做索引会是查询效率提高很多

聚簇索引

聚簇索引是一种数据存储方式,当表有聚簇索引时,数据行实际存放在索引的叶子页。属于聚簇表示数据行和相邻的键值紧凑地存储在一起。因为无法把数据行放在两个不同的地方,所以一个表只能有一个聚簇索引。

聚簇索引实际上是b-tree+数据行

image1

Mysql的内建存储引擎都不支持服务器选择哪个索引作为聚簇索引,InnoDB通过主键聚集数据,即上图中被索引的列是主键列。

聚簇索引的优点:

聚簇索引的缺点

下面看下MyISAM和Innodb对于索引实现的区别,创建如下的数据表,插入10000行数据

create table layout_test(
    col1 int not null,
    clo2 int not null,
    PRIMARY KEY(col1),
    KEY(col2)
)

对于MyISAM引擎,如下:

数据列如下


image1

主键索引如下


image2

col2索引如下


image3

可以看到主键索引和普通索引没有什么区别,叶子页都存放的是行号,用来索引到具体的数据行。

对于Innodb引擎,聚簇索引如下:


image4

可以看到innodb主键索引的叶子节点存放的就是行数据。每个叶子节点都包含了主键值,事务ID,用于事务和MVCC的回滚的指针,以及剩余的列。

还有一个MyISAM和Innodb的二级索引和聚簇索引很不相同。Innodb的二级索引的叶子节点不是行指针,而是主键值,并用主键值当做行指针,这样就减少了当前行移动或者数据页分裂时二级索引的维护工作。innodb在移动行时不需要更新二级索引上的指针。

二级索引列的数据如下:

image5

下图可以清楚看出MyISAM和Innodb的区别

image6

一个问题:在InnoDB表中按主键顺序插入行的两种方案,一种主键是递增的id,一种主键是uuid,哪种插入速度会比较快?

两者的区别如下
顺序:


image7

uuid:


image7

所以当随机值加入后,需要做一次OPTIMIZE TABLE来重新建表并优化页的填充

覆盖索引

如果索引的叶子节点就包含所有要查询的数据,就称改索引为"覆盖索引"。例如对表中某两个字段(A,B)做了联合索引,则如果一个Sql语句是查询A,B两个字段,那就可以用到覆盖索引。

覆盖索引的好处:

覆盖索引必须要存索引列的值,这个在MySQL只能用B-Tree,哈希全文空间都不行。

在使用覆盖索引时,explain时会看到extra有"Using index"

image6

如果想查一个表的所有数据,但是又想用到覆盖索引,可以用延迟关联的方法

image6

innodb所有二级索引都包含主键,所以本来用到覆盖索引的再增加主键值,依然可以用到覆盖索引。

使用索引做排序

在rental表添加一个包含三个列的索引(rental_date,inventory_id,customer_id)

如下语句可以用到索引

select * from rental where rental_date='2019-01-01' order by inventory_id,customer_id 

下面这个也可以,满足了最左前缀的要求

select * from rental where rental_date='2019-01-01' order by inventory_id desc

下面这个也可以,因为orderBy使用的也是最左前缀

select * from rental where rental_date>'2019-01-01' order by rental_date,inventory_id desc

下面这个不能用,因为orderBy的排序方向不同,索引列都是正向排序的

select * from rental where rental_date>'2019-01-01' order by inventory_id desc,customer_id ASC

这个也不行,用到了一个非索引的列

select * from rental where rental_date>'2019-01-01' order by inventory_id ,staff_id

这个也不行,无法组成最左前缀

select * from rental where rental_date>'2019-01-01' order by customer_id

这个也不行,第一列是范围查询

select * from rental where rental_date>'2019-01-01' order by inventory_id desc,customer_id

这个也不行,也是一种范围查询

select * from rental where rental_date='2019-01-01' and inventory_id in (1,2) order by inventory_id desc,customer_id

压缩索引

MyIsam用前缀压缩减小索引大小,从而让更多索引放入内存

冗余和重复索引

Mysql可以对相同列创建多个索引,主键、唯一键也是通过索引实现的。

例如(A,B)和A这种索引就是冗余索引,因为对于(A,B)只查A也走索引

另外一种情况是(A,ID)也是冗余的,因为InnoDB主键列在二级索引中。

索引和锁

InnoDB只有在访问行的时候会对其加锁,但是这个只有在InnoDB在存储引擎层能过滤掉所有不需要的行时才有效。在InnoDB检索到数据行返回给服务层,MySQL服务器才能应用Where子句。这时候很多行已经被锁住,直到服务器过滤掉行之后才释放。

set autocommit=0;
begin;
select actor_id from actor where actor_id<5 and actor_id <>1 for update

只返回3行记录,但是实际上会锁住1-4这四行。通过explain可以看出Extra有"UsingWhere",说明MysSQL是InnoDB将行返回再应用where条件过滤。

set autocommit=0;
begin;
select actor_id from actor where actor_id=1 for update

这个程序会挂起,直到第一个事务释放了锁。

如果说查询根本没用到索引,情况会更糟糕,Innodb会全表扫描并锁住所有的行,这个在生产环境是很危险的。所以写for update语句一定要看看有没有用到索引。

一个很少人知道的细节:InnoDB在二级索引上使用读锁,在访问主键索引需要排他锁,这消除了使用覆盖索引的可能性,并且使得select for update 比 lock in share mode 慢很多

总结

Mysql大多数情况下都会使用B-Tree索引,其他索引都只是适用于特殊目的。

选择索引和编写索引的查询时,有以下三条原则:

祝大家编码愉快,工作愉快,欢迎关注我的公众号,一起分享交流


Java技术小栈
上一篇 下一篇

猜你喜欢

热点阅读