IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> MySQL高性能索引设计 -> 正文阅读

[大数据]MySQL高性能索引设计

欢迎关注我的公众号:技术小白成长记

浅谈索引

B+Tree 索引

面试时常会问B+树和B树的区别,可以参考这篇文章:B树和B+树的区别

MySQL InnoDB引擎采用B+树作为索引结构,按照叶子节点是否存储数据划分,可以分为两类:主键索引和非主键索引(非主键索引有时也被称为普通索引或者二级索引)。

考虑创建如下表,并插入如下记录(100,1,‘a’)、(200,2,‘b’)、(300,3,‘c’)、(400,4,‘d’) 和 (500,5,‘e’)

create table T(
  id int primary key, 
  k int not null, 
  name varchar(16),
  index (k)
) engine=InnoDB;

对应的索引树如下图所示:普通索引的叶子节点除了存储索引列以外,还会存储对应的主键值。而主键索引的叶子节点则会存储整行数据。因为这个差异,当我们执行 select * from where k = 2 的时候,会先去k对应的普通索引树上找到k = 2对应的主键值200,然后再去主键索引树上取出id = 200对应的整行数据。而如果查询时直接通过主键索引,就不需要遍历两棵索引树。

img

索引的作用

加索引的目的是为了加速查询,把数据存储起来更多时候是为了把它们用起来。

对于查询,索引起到了如下几方面的作用:

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

如下的讨论是基于MySQL,InnoDB的B+Tree索引而来。

三星索引

高性能MySQL中提到了三星索引(原概念出自于Tapio Lahdenmaki和Mike Leach编写的Relational Database IndexDesign and the Optimizers一书)

  1. 索引将相关的记录放到一起则获得一星
  2. 如果索引中的数据顺序和查找中的排列顺序一致则获得二星
  3. 如果索引中的列包含了查询中需要的全部列则获得三星

要达到高性能索引,我们的目标就是要达到三星的标准。

索引设计

0.索引设计之前的准备

虽然本文是讲的索引设计,但是有些东西是在创建索引前就应该确认好的。只有对每个细节都斤斤计较,索引才能尽可能发挥出它的优势。

在设计表阶段,如果一次查询需要关联很多张表通常是不可取的,我们可以考虑冗余一些字段反范式来提升查询效率。设计字段的时候,应该尽量使用可以正确存储数据的最小数据类型。更小的数据类型通常更快,因为它们占用更少的磁盘、内存和CPU缓存,并且处理时需要的CPU周期也更少。通常来说:

1.能使用int的就不要用字符串类型。int类型的数据的存储和比较通常要比字符串类型更复杂

2 能使用char的就不要用varchar。例如用CHAR(1)来存储只有Y和N的值,如果采用单字节字符集只需要一个字节,但是VARCHAR(1)却需要两个字节,因为还需要一个字节来记录字符串长度

3.能使用varchar(5)的就不要用varchar(200)。使用VARCHAR(5) 和VARCHAR(200)在磁盘上存储 ‘hello’ 的空间开销是一样的。那么使用更短的列有什么优势呢?更长的列会消耗更多的内存,因为MySQL通常会分配固定大小的内存块来保存内部值。当使用内存临时表进行排序或操作时会遇到性能问题,本来hello只需要5个字节,但是在内存中却需要开辟200个字节来存放

4.尽量避免NULL。如果查询中包含可为NULL 的列,对MySQL来说更难优化,因为可为NULL 的列使得索引、索引统计和值比较都更复杂。可为NULL 的列会使用更多的存储空间,在MySQL里也需要特殊处理。当可为NULL 的列被索引时,每个索引记录需要一个额外的字节

1.确定查询需要的索引

如何能达到一星索引呢?

对于查询的列能用到对应的索引,我们需要根据实际的需求来确定,确定查询,排序,以及分组需要用到的索引。是根据某一个列创建一个索引,还是根据几个列创建一个联合索引,这些都是需要考虑的问题。

对于 where a = 1 and b = 2 我们可以创建一个联合索引(a,b),创建了联合索引 (a,b) ,相当于还增加了 (a) 这个索引,意味着就不用单独在a列上创建一个单独的索引(a)。

但是如果需要执行 where a = 1 order by id(这里的id是主键id),为了提升排序效率,可能还是需要创建一个单独的索引(a)。(a, b)联合索引的叶子节点上不仅有a,b还有主键id,索引为 (a,b,id),执行where a = 1 and b = 2 order by id 时where 后面的查询以及 order by 都可以使用(a, b)这个联合索引。

执行 where a = 1 order by id 时,a = 1 查询可以走 (a,b)联合索引,但是 order by id就没办法走索引,因此 order by id 就需要走文件排序,在explain 输出时,在Extra 可以看到 Using filesort 这个提示,走文件排序就意味着排序时不能使用索引,mysql 需要在得到结果集以后再进行一次排序操作才能返回数据,如果能避免这一步直接使用索引来排序就再好不过。

为查询字段建立了索引以后,通过explain查看查询也的确走索引了,但是也不是说走了索引就万事大吉了,还需要考虑一些情况来进行综合的取舍。

比如:如果是where actor_id = 1 and film_id = 1 这个查询,我们应该是建立一个联合索引 key_actor_film(actor_id, film_id),而不是在actor_id和film_id上分别单独建立一个索引。

而对于 where actor_id = 1 or film_id = 1,如果创建一个联合索引 key_actor_film(actor_id, film_id),actor_id = 1 是 可以走索引的,但是 film_id = 1 这个查询却没办法走 key_actor_film 这个索引。

虽然key_actor_film 这个索引包含了actor_id 和 film_id 两个字段,但是联合索引本质是先按照 actor_id 排序,当 actor_id 相等时再按照 film_id 进行排序,整体看这颗索引树,actor_id 是有序的,film_id 是无序的,当且仅当 actor_id 有序时 film_id 才有序。因此我们可以用这颗索引树单独查询 actor_id,却没办法单独查询 film_id。

因此我们还需要在 actor_id和film_id这两列单独加两个索引,而不是使用一个联合索引。当我们在 actor_id 和 film_id 上分别添加了一个索引,用 explain 查看 where actor_id = 1 or film_id = 1 这个查询时,会发现 type = index_merge,这表示mysql需要对多个索引做一些合并,服务器需要对多个索引做联合操作时(多个OR 条件),通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据的时候,合并的成本将会更大。而优化器不会把这些计算到“查询成本”(cost)中,优化器只关心随机页面读取。单单看扫描行数的话,就会低估其他的查询成本,导致该执行计划还不如直接走全表扫描,这样的情况我们需要尽可能避免。

explain 输出结果:

mysql> explain select film_id, actor_id from film_actor where actor_id = 1 or film_id = 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
   partitions: NULL
         type: index_merge
possible_keys: PRIMARY,idx_fk_film_id
          key: PRIMARY,idx_fk_film_id
      key_len: 2,2
          ref: NULL
         rows: 29
     filtered: 100.00
        Extra: Using union(PRIMARY,idx_fk_film_id); Using where
1 row in set, 1 warning (0.00 sec)

2.确定合适的索引顺序

在确定了索引以后,我们需要考虑如何来安排索引的顺序,这样才能达到二星索引。

创建联合索引 key(a, b, c)时,我们需要考虑到列的排列顺序,是该按(a,b,c)来进行排列呢,还是按照(c,b,a)来进行排呢?索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列,等等。对于查询,排序,分组而言,索引列的排列顺序会极大影响其操作性能。

通常情况下,我们都会主张索引需要有区分度,创建联合索引时,区分度越高的列尽可能越往前放。对于 where 查询来说,区分度高的列放在索引前面,查询时过滤的效率就越高(区分程度怎么看?对于某个列a,可以使用 count(distinct a) / count(*) 作为参考,值为1则表示区分度最高,a这一列不存在重复的值,基于a索引的查询效率非常高)

那为什么说是通常情况呢,因为很多情况下我们还得结合具体的业务场景,具体的问题具体分析,可能还需要考虑到某个列的使用频率,综合考虑排序,分组等等来进行判断。

假设有一张表记录了每个国家的用户,其中有 country(国家) 和 sex(性别) 这两列。sex的字段的区分度很低,不出意外这个字段只会有两个值。但是country这个列却可能会有一两百个值,单独论区分度 conuntry 远远大于 sex。但是业务通常的查询需求是:确定一个性别,然后查询指定范围国家的数据,确定性别,根绝国家进行排序。查询通常为 where sex = ‘f’ and country in (‘America’, ‘Spain’) 以及 where sex = ‘f’ order by country 。这个时候,我们就需要把索引顺序安排为:key_sex_country(sex, country)。如果我们强行按照区分度高的放前面,联合索引就变为了key_country_sex(country, sex),排序就不能走索引了。

3.尽可能使用覆盖索引

对于Mysql而言最好的索引就是使用覆盖索引,这样才能达到三星索引。

主键索引和其他索引不一样的地方在于,主键索引(聚簇索引)的叶子节点存储的是数据行(可以理解为主键索引树存储了整张表),而其他索引的叶子节点存储的是主键的值。

我们如果是通过主键索引树来进行查询,不管是select部分字段,还是select所有字段 (select *),都能在一次查询中搞定。如果我们使用的是普通索引,索引中的字段不能满足查询所需结果,就需要先在普通索引树上查到数据行对应的主键,然后去主键索引树上查询到对应的数据行,这个过程称为回表,回表的过程会带来额外的开销。

如果使用某个索引树查询不需要回表,我们就称这样的索引为覆盖索引,主键索引自然也是覆盖索引,覆盖也就表示当前使用到的索引树能够满足查询结果。

使用覆盖索引,无须回表能带来哪些好处?

1.减少随机IO。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。扫描索引本身是很快的,只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就都回表查询一次对应的行,这个过程基本上都是随机I/O,按索引顺序去读取一行数据通常比顺序的扫描全表读取一行数据慢得多

2.减少数据访问量。索引条目通常远小于数据行大小,所以如果只需要读取索引,那MySQL就会极大地减少数据访问量。索引的数据比数据行更小,更容易全部放入内存中

3.**范围查询更快。索引是按照列值顺序存储的(至少在单个页内是如此),所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据的I/O要少得多

如何查看是否使用了覆盖索引?可以通过 explain 命令查看 Extra字段是对应的值是否为 Using index,Using index 表示使用了覆盖索引。注意,不要和type 列的值 index 混淆了,type = index 说明MySQL使用了索引扫描。

示例,有一张表film_actor,该表的主键是由 actor_id, film_id 这两个字段联合而来:PRIMARY KEY (actor_id, film_id),输出如下:

mysql> explain select film_id from film_actor where actor_id = 1 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
   partitions: NULL
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: const
         rows: 19
     filtered: 100.00
        Extra: Using index
1 row in set, 1 warning (0.00 sec)

mysql> 

sql查询没有使用覆盖索引时,可以综合考虑能否改写成使用覆盖索引。

对于如下查询,sql1 可以改写为 sql 2

改写前:products 表 中 prod_id 为主键,在actor上有一个普通索引

-- sql1
SELECT
	* 
FROM
	products 
WHERE
	actor = 'SEAN CARREY' 
	AND title LIKE '%APOLLO%';

改写后:将actor上的索引修改为联合索引 (actor, titile),在执行 WHERE actor = ‘SEAN CARREY’ AND title LIKE ‘%APOLLO%’ 查询时,actor 可以走索引,title是没办法通过索引来加速查询,但是可以利用索引对title进行过滤,这样就不用每查询到actor = ‘SEAN CARREY’ 的节点就回表去判断title的值了。

-- sql2
SELECT
	* 
FROM
	products
	JOIN ( SELECT prod_id FROM products WHERE actor = 'SEAN CARREY' AND title LIKE '%APOLLO%' ) AS t1 
  ON (t1.prod_id = products.prod_id)

这种方式叫做延迟关联(deferred join),因为延迟了对列的访问。在查询的第一阶段MySQL可以使用覆盖索引,在FROM 子句的子查询中找到匹配的prod_id ,然后根据这些prod_id 值在外层查询匹配获取需要的所有列值。虽然无法使用索引覆盖整个查询,但总算比完全无法利用索引覆盖好,sql2的做法能尽可能减少回表的次数。

优化结果取决于 actor = ‘SEAN CARREY’ 以及 title LIKE ‘%APOLLO%’ 的数据集大小

1.如果 actor = ‘SEAN CARREY’ 的数量很大,而 title LIKE ‘%APOLLO%’ 的数据量比较小,那么优化效果比较明显。按照sql1,凡是在索引树上查询到 actor = ‘SEAN CARREY’ 的记录,都需要回表查询出对应的数据行来判断 title值。而 sql 2使用了覆盖索引做了一次过滤,能极大减少回表次数。

2.如果 actor = ‘SEAN CARREY’ 的数量比较小,即便在索引树上每查询到一次 actor = ‘SEAN CARREY’ 的节点就回表一次,总的回表次数也不多。甚至sql1性能可能会更好,因为sql更简单,执行计划也简单。

  1. 如果 actor = 'SEAN CARREY 和 title LIKE ‘%APOLLO%’ 的数据量都很大,sql1和sql2的性能也差不多,因为不管怎样都需要去回表很多次。

4.考虑索引带来的问题

使用索引提升了查询效率,但是也带来了额外开销:

  1. 增加索引会降低数据插入或更新的效率,因为插入或者更新数据时,需要去维护索引
  2. 索引会带来额外的占用空间,有的表索引所占的空间甚至要远大于数据行所占的空间
  3. 增加索引会对事务增加负担,mysql需要做更多的操作来保证事务性

本文参考

  • 《高性能MySQL第三版》
  • 《MySQL实战45讲》极客时间
  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-06-06 17:25:11  更:2022-06-06 17:27:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 21:36:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码