高性能mysql第五章——创建高性能的索引

第五章 创建高性能的索引

ORM(对象关系映射)只能产生合理的查询,而非常非常非常非常难产生适合索引的查询。

5.1 B-Tree索引

虽然叫它B-Tree索引,但是不同的存储引擎会使用不同的数据结构。有的使用T-Tree,而InnoDB使用B+Tree

不同的存储引擎实现B-Tree的方式也各不相同。MyISAM使用前缀压缩技术使得索引更小,一页可以放下更多数据,InnoDB则是按原数据格式存储。MyISAM使用行的物理位置引用被索引的行,InnoDB使用行的主键

怎么加快查询速度?

使用了B-Tree索引,存储引擎不再需要扫全表来查询数据,而直接从树的根结点开始查询,对比索引值来找到子节点,从而找到对应数据或数据不存在。

根结点与叶子节点之间可能存在很多层节点页,取决于表的大小,InnoDB默认一页为16KB。

设有如下数据表:

create table people
(
	last_name varchar(50) not null,
	first_name varchar(50) not null,
	brith date not null,
	gender enum('m','f') not null,
	name_brith_key(last_name,first_name,brith)   # 三列的联合索引
);

B+Tree的结构大体如下所示:
高性能mysql第五章——创建高性能的索引_第1张图片

B-Tree索引适用的查询
  • 全值匹配 。和索引中的所有列进行匹配。
  • 匹配最左前缀。如last_name, (last_name, first_name)
  • 匹配列前缀。匹配每一列的前缀。
  • 范围匹配。
B-Tree索引不适用的查询
  • 非最左前缀列匹配。如(last_name, brith), (first_name, brith)
  • 查询中某个列有模糊匹配,则之后的查询不再使用索引。如 where last_name='fan' and first_name like '%a' and brith='1997-01-01'。first_name使用了模糊匹配,所以brith不会使用索引查询。

5.2 哈希索引

哈希索引基于哈希表实现,只适用于精确查询

若使用了哈希索引,存储引擎会根据 索引列的值和主键,计算出一个哈希值,将哈希值存储下来,并将指向行的物理位置存储到哈希表中。

在mysql中,只有Memory引擎显示支持哈希索引。InnoDB为自适应性哈希索引,即当InnoDB注意到某些索引所用的非常频繁时,会在内存中基于B+Tree之上再创建一个哈希索引。

优点

只需存储哈希值,存储紧凑,查询速度快

限制
  • 每次查询必须要读取行,无法根据索引中的其他字段直接返回。不过内存中读取行非常快,一般不视为瓶颈
  • 无法用于排序
  • 不支持部分索引查找,如(A,B)索引无法只查找A。
  • 只支持等值查询,如 =,in。原因同无法排序一样。

5.3 索引的优点

  • 索引大大减少了服务器需要扫描的数据量
  • 索引可以帮助服务器避免排序和临时表
  • 索引可以将随机I/O变成顺序I/O

索引并不总是最好的工具。对于特别小的表,通常全表扫描更高效。对于中到大型的表,建立索引更高效。而对于非常大的表,建立和使用索引的代价将随之增长。

5.4 如何高性能的使用索引?

独立的列

如果查询中的列不是独立的列,则Mysql不会使用索引。也就是若查询中含有表达式、函数式,则对该列不会使用索引。

select name from people where age + 1 = 20;   表达式
select name from people where to_days(current_date) - to_days(brith) <= 100;  函数式
前缀索引和索引选择性(不太常用)

有些时候,若我们想为很长的varchar和text等字段加索引,会让索引又大又慢。通常我们可以选择前缀索引,即索引前n个字符,这样可以大大节约空间,又提高效率。索引选择性即不重复的索引条数/数据条数

如果确定n的大小呢?自然是又要节省空间,又要保证索引选择性。可以通过两种方式来选择n

1.
// 获得原来文本的重复度
select count(*) as cnt,city
from people
group by city order by cnt DESC limit 0,10;

// 试探前缀的文本重复度,若数据分布与原来文本相差不大,则ok
select count(*) as cnt, left(city,4) as pref
from people
group by pref order by cnt DESC limit 0,10;

2.
// 计算原来文本的索引选择性
select count(distinct(city)) / count(*) from people;

// 计算n个前缀的索引选择性,若相差不大,则ok
select count(distinct(left(city,4))) / count(*) from people;

// 添加前缀索引
alter table people add key (city,4);
不对每个列都创建单独的索引

对一张表,若不创建联合索引,而对多个列都创建单独的索引,对与大部分查询都不友好。

select city,name from people
where gender=1 or age=18;

在mysql<5.0时,对于以上的查询会进行全表扫描。而在mysql>=5.0时,mysql对这类查询会使用 索引合并,同时使用两个单列索引进行查询,并将结果合并,会浪费很多的cpu和内存资源在合并、排序上。有三种情况:OR条件的联合、AND条件的相交、组合前两种情况的联合和相交。

当我们在explain一条sql语句时,若extra字段出现了索引合并,证明索引建的非常糟糕。
在这里插入图片描述

多列索引的顺序

若我们确定要为多个列加索引,那么索引的顺序是如何的呢?一般考虑如下几个方面。

  • 选择性的高低
  • 是否含有order by、group by、distinct操作
  • 数据分布的均匀程度,谁过滤的行数是最多的

若不考虑order by、group by、distinct操作,则选择性最高且过滤行数最多的,应该在前面。
若考虑order by、group by、distinct操作,由于B-Tree树建立是以索引顺序进行排序的,则应综合考虑。

5.5 聚簇索引

聚簇索引不是一种索引类型,而是一种数据存储方式,其实就是一张表。不是所有的存储引擎都支持聚簇索引,InnoDB的聚簇索引,是一棵B+Tree,key为表的主键,叶子节点存储数据行。若该表没有主键,则会选择一个唯一非空索引,若也没有,则会隐式定义一个主键。

聚簇索引的意义,就是将数据按照某一字段聚集在一起,从而减少磁盘IO操作,使查询更快。
高性能mysql第五章——创建高性能的索引_第2张图片
聚簇索引可能对性能有帮助,也可能导致严重的性能问题。

优点

  • 可以把相关数据保存在一起。例如一个邮件系统,以用户ID作为主键来聚集数据,在获取用户全部邮件时,从磁盘读取有限的页就可以得到数据,否则或许每一封邮件都要进行一次磁盘IO。
  • 聚簇索引是以B+Tree存储的,查询性能更快。
  • 二级索引在数据物理位置改变时无需维护。
  • 使用覆盖索引扫描的查询,可以直接利用页节点中的主键值。

缺点

  • 聚簇索引最大限度的提高了IO密集型应用的性能。但如果数据全在内存里,就没有这个必要了。
  • 插入速度严重依赖插入顺序。
  • 聚簇索引更新主键的代价很高,所有数据行都要换个位置存储。
  • 聚簇索引的表,在插入新行或者更新主键时,可能会产生页分裂,占用多余的磁盘空间。
  • 聚簇索引在行很稀疏时,会使全表扫描更慢。
  • 二级索引的叶子结点data数据可能会比指针大。
  • 二级索引访问数据需要两次索引查找。

聚簇索引与非聚簇索引的区别
高性能mysql第五章——创建高性能的索引_第3张图片
对于主键索引而言:

  • InnoDB的主键索引,其实是一张表。建立一棵B+Tree,key为主键值,叶子结点存储表的其他字段、事务ID、回滚指针等表需要存储的信息。
  • MyISAM的主键索引,与二级索引并没有什么不同。表在磁盘中一行一行顺序存储。

对于二级索引而言:

  • InnoDB的二级索引,叶子结点存放的是主键值。可能所需空间会更大,但数据行迁移(改变物理位置时)无需改变二级索引值。
  • MyISAM的二级索引,叶子结点存的是物理地址(偏移量)。

按主键顺序插入行

如果正在使用的InnoDB表没有什么数据需要聚集,那么可以定义一个自增键作为主键。这样可以保证数据行是按顺序写入的,性能也会更好。

为什么自增主键性能会更好呢?因为自增主键会使数据按顺序写入,直接插入最后面即可,不会产生页分裂,若当前页满了,则直接写入下一页。

而如果是非自增主键,主键值 离散 且 分布范围很大。那么一页内的数据,主键不是连续的,如:一页内主键有1,5,18,此时有一个主键为4的数据行插入,则需要移动5,18位置,还有可能会产生页分裂。

因此,使用自增主键 与 使用离散且分布范围很大的主键相比,查询、插入等等性能是低效很多的。 id bigint(20) unsigned NOT NULL AUTO_INCREMENT (自增锁)

5.6 覆盖索引

包含查询中所出现的所有字段(不仅仅只是where之后的字段) 的索引称为覆盖索引。覆盖索引可以让查询只需查找二级索引,而无需再查找聚簇索引。减少了数据的访问量,减少了缓存压力,提高了查询速度。

extra字段中的using index表示使用了覆盖索引;using where 表示索引覆盖了where条件。

5.7 前缀压缩索引

MySIAM在B-Tree的key值存储上使用了前缀压缩技术,默认只压缩字符串。如:第一个key值为"fancy",第二个key值为"fancy cute",那么第二个存储为"5, cute"类似的形式。由此可见,前缀压缩使得每个索引都依赖前面的索引值,对倒序查询相当不友好,并且要花更多的时间,来换取将更多的索引加载到内存中。

5.8 索引和锁

mysql只有在访问行的时候才会为行加锁,索引可以减少存储引擎访问行的次数。

如果索引无法过滤掉行,那么存储引擎层会为读取的行都加锁,返回给服务器。在服务器过滤掉某一行之后,再释放锁,更早的版本甚至要到事务结束之后,再为所有行解锁。

你可能感兴趣的:(DB)