文章目录

  • 浅谈索引
    • B+Tree 索引
    • 索引的作用
  • 三星索引
  • 索引设计
    • 0.索引设计之前的准备
    • 1.确定查询需要的索引
    • 2.确定合适的索引顺序
    • 3.尽可能使用覆盖索引
    • 4.考虑索引带来的问题
  • 本文参考

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

浅谈索引

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对应的整行数据。而如果查询时直接通过主键索引,就不需要遍历两棵索引树。

索引的作用

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

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

  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: 1select_type: SIMPLEtable: film_actorpartitions: NULLtype: index_merge
possible_keys: PRIMARY,idx_fk_film_idkey: PRIMARY,idx_fk_film_idkey_len: 2,2ref: NULLrows: 29filtered: 100.00Extra: 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: 1select_type: SIMPLEtable: film_actorpartitions: NULLtype: ref
possible_keys: PRIMARYkey: PRIMARYkey_len: 2ref: constrows: 19filtered: 100.00Extra: Using index
1 row in set, 1 warning (0.00 sec)mysql>

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

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

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

-- sql1
SELECT*
FROMproducts
WHEREactor = '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*
FROMproductsJOIN ( 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讲》极客时间

MySQL高性能索引设计相关推荐

  1. php 如何设计索引_Mysql学习浅谈mysql的索引设计原则以及常见索引的区别

    <Mysql学习浅谈mysql的索引设计原则以及常见索引的区别>要点: 本文介绍了Mysql学习浅谈mysql的索引设计原则以及常见索引的区别,希望对您有用.如果有疑问,可以联系我们. 索 ...

  2. MYSQL数据库索引设计的原则

    索引说明 InnoDB是使用B+树来实现其索引功能的.在B+树中,内节点(非叶子节点)存储了行数据的键,而叶子节点存储了所有的行数据,而B树的每个节点都存储了真实的数据.这种数据结构,决定了两者有以下 ...

  3. 20个MySQL高性能架构设计原则(收藏版)

    墨墨导读:MySQL如何实现高性能?以下内容是结合其他技术同仁的总结和自我实践整理的20个开源数据库设计原则,分享至此,希望对大家有帮助. 开源数据库架构设计原则 01 技术选型 选择成熟的平台和技术 ...

  4. Mysql高性能索引

    一.索引是什么 二.索引的底层实现原理 三.InnoDB的存储结构是怎样的? 四.InnoDB索引和MyIsam索引对比 五.Mysql为什么会选错索引 六.唯一索引和普通索引的区别 导读:本博文讲解 ...

  5. BTA高频面试题——MySQL的索引设计原则以及常见索引的区别

    http://mp.weixin.qq.com/s?__biz=Mzg3MTA3MTI2OA==&mid=2247483711&idx=1&sn=b3a89b8720b6fbf ...

  6. mysql之索引的工作机制

    mysql之高性能索引 当db的量达到一定数量级之后,每次进行全表扫描效率就会很低,因此一个常见的方案是建立一些必要的索引作为优化手段,那么问题就来了: 那么什么是索引呢? 索引的实现原理是怎样的? ...

  7. MySQL 高级 —— 索引实现的思考

    引言 最近看了一个公开课,是有关MySQL对索引设计的思考.详细讲解了几种索引实现的设计思考与利弊辨析,讨论了为什么MySQL默认情况下会使用B树索引,B+树索引又对B树做了哪些结构改进. 本片博客通 ...

  8. 【Mysql性能调优】高性能表结构及索引设计的最优实践

    数据库表设计 良好的表结构设计是高性能的基石,应该根据系统将要执行的业务查询来设计,这往往需要权衡各种因素.糟糕的表结构设计,会浪费大量的开发时间,严重延误项目开发周期,让人痛苦万分,而且直接影响到数 ...

  9. MySQL优化系列(二)--查找优化(1)(非索引设计)

    MySQL优化系列(二)--查找优化(1)(非索引设计) 接下来这篇是查询优化,用户80%的操作基本都在查询,我们有什么理由不去优化他呢??所以这篇博客将会讲解大量的查询优化(索引以及库表结构优化等高 ...

最新文章

  1. 如何在本地站点打开html,如何在本地运行的网页上创建指向本地文件的链接?...
  2. 【数据挖掘笔记六】挖掘频繁模式、关联和相关性:基本概念和方法
  3. 一个web左侧菜单例子
  4. Linux 精准获取进程pid--转
  5. linux自动挂起什么意思,Linux中进行挂起(待机)的命令说明
  6. Netty--ChannelHandler和ChannelPipeline
  7. GD32如何替换STM32?
  8. 服务器环境~某个页面无法访问的处理
  9. 《java设计模式》之责任链模式
  10. dos命令行设置网络优先级_替代windows系统下cmd的10款命令行工具
  11. 基于 Springboot 和 Mybatis 的后台管理系统 BootD
  12. 深度学习与TensorFlow: VGG论文笔记
  13. 修改Linux系统默认编辑器
  14. NPS内网穿透服务端一键安装脚本
  15. odoo开发笔记 -- 翻译机制及导入.po文件
  16. web安全day11:进一步学习windows域的gpo
  17. 如何使用 Cisdem Video Converter 在 Mac 上将 MOV 转换为 MP3
  18. 研磨设计模式之《模板方法模式template method》
  19. estore简版商城
  20. LR2021下载LR2021最新10.1.0下载安装Lightroom最新下载1分钟看完就能自己操作

热门文章

  1. 办理北京居住证需要的材料
  2. php 导出多个excel并输出压缩文件
  3. 艾美捷SequENZ测序级改造型胰蛋白酶特异性和应用
  4. ubuntu设置开机启动图形应用程序,替换默认图形桌面
  5. 物联网环境监测数据中心-物联网项目开发
  6. evernote国际版不可用
  7. qlineedit 获取文本_Python如何获取QLineEdit文本?
  8. 很喜欢这句话:将军赶路,不追小兔
  9. 物联卡先用流量包还是套餐流量,物联卡流量扣除顺序是什么?
  10. 3.10 Maya历法