第5章 创建高性能的索引


本文为《高性能Mysql 第三版》第四章读书笔记,Mysql版本为5.5

索引基础

索引的重要性:找一本800面的书的某一段内容,没有目录也没有页码(页码也可类比是索引)

索引类型

  • B-Tree 索引 : 可用于全值匹配、最左前缀匹配、列前缀匹配、范围值匹配、精确匹配某一列并范围匹配另外一列、只访问索引的查询 ,原文截图:

  • 哈希索引 : 只适用于精确匹配查询,不适用于范围查询

  • 空间数据索引 : 可以有效地使用任意维度来组合查询

  • 全文索引 : 做的事情类似于搜索引擎,而不是简单的 where 条件匹配

对于Mysql索引使用的歧义

对于很早的版本,一直强调:最左前缀匹配,索引使用顺序,不可跳过索引中的列等等,但高性能Mysql一书之后的版本中对于

mysql优化器进行了很大程度的优化,使得开发者在使用时候可以不用顾虑太多

例如:

CREATE TABLE `mytest`.`student`  (
`age` int(11) NULL DEFAULT NULL COMMENT 'age',
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'name',
`year` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'year',
INDEX `demo`(`age`, `name`, `year`) USING BTREE COMMENT 'demo'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

故意使用不按索引顺序查询:

mysql> explain extended  SELECT * FROM `student` where `name` = 1 and `age` = 1;
实际: key:demo 使用到了索引

如何查看Mysql优化之后的SQL

# 仅在服务器环境下
explain extended SELECT * FROM `student` where `name` = 1 and `age` = 1; # 再执行
show warnings; # 结果如下:
/* select#1 */ select `mytest`.`student`.`age` AS `age`,`mytest`.`student`.`name` AS `name`,`mytest`.`student`.`year` AS `year` from `mytest`.`student` where ((`mytest`.`student`.`age` = 1) and (`mytest`.`student`.`name` = 1))

可以发现真正执行的SQL是 age在前,name在后

对于仅使用year字段的查询结果呢?依然显示 -> key:demo 使用到了索引

总结:

  • 写代码时对于索引顺序不必有太多顾虑
  • 优化SQL时候不仅仅只看 key参数是否使用索引,还需要注意type,ref等等
  • Mysql 优化器并不是一定改变SQL的参数查询顺序,而是以它的判定方式选择它认为的最有效的查询

索引的优点

正常的索引数据结构可以有其独特的优点,比如哈希索引不支持顺序查询,而B-Tree,或者 B+Tree则可以,总结B-Tree系列索引优

点如下:

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

高性能的索引策略

独立的列

索引在使用中不可嵌入表达式或者方法错误示范: select * from where eid + 1 < 10;

前缀索引和索引的选择性

如果索引很长的字符串列,会导致索引变得很大并且很慢。一个处理策略是模拟哈希索引,另一个办法是索引开始的一部分字符串的值,即前缀索引。

索引的选择性指不重复的索引值和数据表中记录总数(#T)的比值。取值在为(1/ #T~1]。选择性越高则查询效率越高。唯一索引的选择性是1,是最好的索引选择性,性能也是最好的。

前缀索引的优点是可以节约索引空间,提高索引效率。缺点是降低了索引的选择性,同时也无法使用前缀索引做ORDER BY 或 GROUP BY 操作,无法使用前缀索引做覆盖扫描。

使用前缀索引时,确定前缀长度的依据是:计算前缀索引的选择性,使前缀索引选择性接近完整列的选择性

多列索引

错误做法:为where字段的每一个列创建独立的索引,或者按照错误的顺序创建多列索引

在explain中,如果发现索引合并,实际上说明了表上的索引建的不够好:

  • 当需要进行AND操作时,其实说明了需要建立一个包含所有相关列的多列索引;

  • 当需要进行OR操作时,通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上;

  • 优化器不会把这些计算到”查询成本“中,这会使得查询成本被低估,可能还不如全表扫描然后union。

选择合适的索引顺序

不考虑排序和分组时:将选择性最高的列放在前面通常是很好的(选择性即有效区分数据)

但,性能不只是依赖于所有索引列的选择性,也和查询条件的具体值有关,也就是和值的分布有关,这就意味着可能需要

根据那些运行频率最高的查询来调整索引列的顺序

例如:一张订单表,需要查询的字段有两个,一个是地区ID,一个是用户ID,简单思考就一定能发现用户ID的区分度应该是比地区

ID要高的多(相同用户ID的数据要远远少于相同地区ID),因此建立索引应该是(用户ID,地区ID)

聚簇索引

**聚簇索引:**将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据

**非聚簇索引:**将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置

在innodb中,在聚簇索引之上创建的索引称之为辅助索引,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引。辅助

索引叶子节点存储的不再是行的物理位置,而是主键值,辅助索引访问数据总是需要二次查找

聚簇索引具有唯一性,由于聚簇索引是将数据跟索引结构放到一块,因此一个表仅有一个聚簇索引, 聚簇索引默认是主键

如果表中没有定义主键,InnoDB 会选择一个唯一且非空的索引代替。如果没有这样的索引,InnoDB 会隐式定义一个主键(类似

**oracle中的RowId)**来作为聚簇索引。如果已经设置了主键为聚簇索引又希望再单独设置聚簇索引,必须先删除主键,然后添

加我们想要的聚簇索引,最后恢复设置主键即可

聚簇的数据的优点:

  • 可以把相关的数据保存在一起。如电子邮箱中,根据用户ID来聚簇数据,这样只要读取少数的数据页就可以获取某个用户的全部邮件。如果没有使用聚簇索引,可能每一封邮件导致一次磁盘I/O;

  • 访问数据更快。聚簇索引将索引和数据报错在同一个B-Tree中,因此获取数据通常比非聚簇索引更快;

  • 使用覆盖索引扫描的查询可以直接使用页节点的主键值

聚簇索引的缺点:

  • 聚簇索引能够提高I/O的密集程度,但如果所有的数据全都放在内存中,那么访问顺序就没那么重要了,聚簇索引也就没什么优势了

  • 插入速度严重依赖于插入顺序。按照主键顺序插入是最快的。如果不是先找主键顺序加载数据,那么加载完成后最好用OPTIMIZE TABLE命令重新组织一下表

  • 更新聚簇索引列的代价很高

  • 基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行时,可能面临"页分裂"的问题

  • 举措索引可能导致全表扫描变慢,尤其是行比较稀疏,或者页分裂导致数据存储不连续的时候

  • 二级索引(非聚簇索引)可能比想象的大,因为二级索引的叶子节点包含了引用行的主键列

  • 二级索引访问需要两次索引查找,而不是一次(第一次获得主键,第二次根据主键值去聚簇索引中查找对应的行)

在InnoDB表中按主键顺序插入行

如果正在使用InnoDB表并且没有什么数据需要聚集,那么可以定义一个代理健作为主键。最简单的是使用AUTO INCREMENT自增

列,这样可以保证数据行是顺序写入的,对于主键做关联操作也是最好的

最好避免随机的(不连续且值的分布范围非常大)聚簇索引,特别是对于I/O密集型的应用,例如UUID

用UUID作为主键的坏处:

  • 写入的目标也可能已经疏导磁盘并从缓存中移除,或者还没有被加载到缓存中。InnoDB不得不先找到并且从磁盘读取目标页到内存,这导致了大量的随机I/O

  • 因为写入是乱序的,InnoDB不得不频繁地做页分裂操作,会导致移动大量数据,一次插入最少需要修改三个页而不是一个页

  • 由于频繁的页分裂,页会变的稀疏并被不规则填充,所以最终数据会有碎片

  • UUID较长,不利于作为索引

字符类型作为主键的通用替代方案美团分布式ID生成项目, 雪花ID

覆盖索引

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为:覆盖索引

覆盖索引的好处:

  • 索引条目远小于数据行大小,能够极大地提高性能,所以如果只需要读取索引,那么MySQL就会极大地减少数据访问量

  • 因为索引是按照值顺序存储的,所以对于I/O密集型的范围查询会比随机从磁盘中读取每一行数据的I/O要少的多

如果不覆盖索引,则会产生回表查询, 先定位主键值,再定位行记录,它的性能较扫一遍索引树更低

使用索引扫描来做排序

MySQL有两种方式可以生成有序的结果:通过排序操作;或者按索引顺序扫描

如果EXPLAIN出来的type列的值为index,则说明使用了索引扫描排序

扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引无法覆盖所有的列,那就不得不扫描

一条索引记录就回表查询一次对应的行,这基本上属于随机I/O,因此按索引顺序读取数据的速度通常比顺序地全表扫描要慢

压缩(前缀压缩)索引

MyISAM使用前缀压缩来减少索引的大小,从而让更多的索引可以放入内存中,这在某种情况下可以极大地提高性能。默认只压缩

字符串,但通过参数设置也可以对整数压缩 , MyISAM存储引擎不过多深究

冗余和重复索引

重复索引

  • MySQL允许在相同列上创建多个索引,但这样需要单独维护重复的索引,并且优化查询的时候也需要逐个进行考虑,会影响性能,应该避免这么做

冗余索引

  • 如果已经创建了索引(A, B),在创建索引(A),那么就是冗余索引,因为它只是前一个索引的前缀

  • 冗余索引通常发生在表添加新索引的时候。如增加一个新的索引(A, B),而没有扩展已有索引(A),导致(A)成为冗余索引。或者将索引扩展为(A, 主键ID),对InnoDB来说,主键已经包含在二级索引中了,因此也是冗余的

索引和锁

索引可以让查询锁定更少的行,如果你的查询从不访问那些不需要的行,那么就会锁定更少的行,从两个方面来看这对性能都有好

处:

  • 虽然InnoDB的行锁的效率很高,内存使用也很少,但是锁定行的时候依然会带来额外开销

  • 锁定需要的行会增加所争用并减少并发性

InnoDB只有在访问行的时候才会对其加锁。而索引能够减少InnoDB访问的行数,从而减少锁的数量。但这只有当InnoDB在存储引

擎能够过滤掉不需要的行时才有效,如果索引无法过滤掉无效的行,那么在InnoDB检索到数据并返回给服务器层之后,MySQL服务

器才能应用Where子句,这时已经无法避免锁定行了:InnoDB已经锁住了这些行,到适当的时候才释放。

Explain的Extra列显示“Using Where”,说明MySQL服务器将存储引擎返回行之后再应用where过滤条件。此时where子句以前的数据全都被加锁

InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排它锁(写锁)

InnoDB的行锁是建立在索引的基础之上的,行锁锁的是索引,不是数据,所以提高并发写的能力要在查询字段添加索引

维护索引和表

使用正确的类型创建了表并加上了合适的索引后,还需要维护表和索引来确保它们正常工作,目的如下:

  • 找到并修复损坏的表

  • 维护准确的索引统计信息

  • 减少碎片

找到并修复损坏的表

可以通过CHECK TABLE检查是否发生了表错误

可以用REPAIR TABLE或者一个不作任何操作的ALTER操作来修复表

更新索引统计信息

  • records_in_range(),通过向存储引擎传入两个边界值获取在这个范围大概有多少条记录

  • info(),返回各种类型的数据,包括索引的基数(每个键值有多少条记录)

减少索引和数据的碎片

B-Tree索引可能导致碎片化,会导致查询效率降低。有三类数据碎片

  • 行碎片:数据行被存储在多个片段中

  • 行间碎片:逻辑上顺序的页,或者行在磁盘上不是顺序存储的

  • 剩余空间碎片化:数据也中有大量的空余空间

对于MyISAM表,三类碎片都可能发生,InnoDB不会出现短小的碎片行,会移动短小的行并重写到一个片段中

总结

选择索引以及利用索引查询时的三个原则:

  • 单行访问是很慢的。最好读取的块中包含尽可能多需要的行,使用索引可以创建位置引用以提升效率

  • 按顺序访问范围数据是很快的,原因如下:

    1. 顺序I/O不需要多次磁盘寻道,比随机I/O快

    2. 如果服务器能够按顺序读取数据,那么就不再需要额外的排序操作,并且GROUP BY查询也无须再做排序和将行按组进行聚合计算了

  • 索引覆盖查询是很快的

现实使用中,很难做到每一个查询都有完美的索引,这时候需要根据需求有所取舍地创建合适的索引,而非根据惯例一刀切

【高性能Mysql 】读书笔记(三)的更多相关文章

  1. 高性能MySQL --- 读书笔记(1) - 2016/8/2

    此书不但帮助MySQL初学者提高使用技巧,更为有经验的MySQL DBA指出了开发高性能MySQL应用的途径.全书包括14章,内容覆盖MySQL系统架构.设计应用技巧.SQL语句优化.服务器性能调优. ...

  2. 高性能MySQL --- 读书笔记(2) - 2016/8/2

    第1章 MySQL架构 MySQL架构与其他数据库服务器大不相同,这使它能够适应广泛的应用.MySQL足够灵活,能适应高要求架构.例如Web应用,同时还适用于嵌入式应用.数据仓库.内容索引和分发软件. ...

  3. 高性能mysql读书笔记(一):Schema与数据类型优化

    4.5 加快ALTER TABLE 操作的速度 原理: MySQL 的ALTER TABLE 操作的性能对大表来说是个大问题. MySQL 执行大部分修改表结构操作的方法是用新的结构创建一个空表,从旧 ...

  4. 构建高性能WEB站点笔记三

    构建高性能WEB站点笔记三 第10章 分布式缓存 10.1数据库的前端缓存区 文件系统内核缓冲区,位于物理内存的内核地址空间,除了使用O_DIRECT标记打开的文件以外,所有对磁盘文件的读写操作都要经 ...

  5. 【转载】MDX Step by Step 读书笔记(三) - Understanding Tuples (理解元组)

    1. 在 Analysis Service 分析服务中,Cube (多维数据集) 是以一个多维数据空间来呈现的.在Cube 中,每一个纬度的属性层次结构都形成了一个轴.沿着这个轴,在属性层次结构上的每 ...

  6. MYSQL学习笔记三:日期和时间函数

    MYSQL学习笔记三:日期和时间函数 1. 获取当前日期的函数和获取当前时间的函数 /*获取当前日期的函数和获取当前时间的函数.将日期以'YYYY-MM-DD'或者'YYYYMMDD'格式返回 */ ...

  7. MySql学习笔记三

    MySql学习笔记三 4.DML(数据操作语言) 插入:insert 修改:update 删除:delete 4.1.插入语句 语法: insert into 表名 (列名1,列名2,...) val ...

  8. 【MySQL 读书笔记】RR(REPEATABLE-READ)事务隔离详解

    这篇我觉得有点难度,我会更慢的更详细的分析一些 case . MySQL 的默认事务隔离级别和其他几个主流数据库隔离级别不同,他的事务隔离级别是 RR(REPEATABLE-READ) 其他的主流数据 ...

  9. 【MySQL 读书笔记】全局锁 | 表锁 | 行锁

    全局锁 全局锁是针对数据库实例的直接加锁,MySQL 提供了一个加全局锁的方法, Flush tables with read lock 可以使用锁将整个表的增删改操作都锁上其中包括 ddl 语句,只 ...

  10. 【MySQL 读书笔记】当我们在执行该查询语句的时候我们在干什么

    看了非常多 MySQL 相关的书籍和文章,没有看到过如此优秀的专栏.所以未来一段时间我会梳理读完该专栏的所学所得. 当我们在执行该查询语句的时候我们在干什么 mysql> select * fr ...

随机推荐

  1. ida 动态调试apk

    1,启动 android_x86_server 2 adb forward tcp:23946 tcp:23946 调试应用命令:adb shell am start -D -n com.droidh ...

  2. cb39a_c++_STL_算法_for_each_transform_比较

    cb39a_c++_STL_算法_for_each_transform_比较for_each() 速度快,不灵活transform() 速度慢, 非常灵活 STL算法-修改性算法for_each()c ...

  3. jenkins构建报错 Error fetching remote repo 'origin'

    ERROR: Error fetching remote repo 'origin' Finished: FAILURE // 原因如下 原因一:可能是配置的git分支的权限问题,检查一下配置里面的源 ...

  4. Jmeter系列(29)- 详解 JDBC Connection Configuration

    如果你想从头学习Jmeter,可以看看这个系列的文章哦 https://www.cnblogs.com/poloyy/category/1746599.html 前言 发起 jdbc 请求前,需要有 ...

  5. Laravel 中自定义 手机号和身份证号验证

    首先在 Providers\AppServiceProvider.php 文件中自定义 手机号和身份证号验证 // AppServiceProvider.php 文件 <?php namespa ...

  6. Oracle 导入数据库dmp文件

    场景:windows2008 R2系统 ,往新安装的oracle11g数据库导入同事给的dmp文件到指定的新建的用户. 1.创建表空间 在导入dmp文件之前,先打开查看dmp文件的表空间名称(tabl ...

  7. 一条SQL删除重复记录,重复的只保留一条

    情景: 我们的数据库中可能会存在很多因各种原因而重复的记录,我们需要对这些重复的记录进行删除,每组组重复的记录只保留一条就行 例如我们有这么个表:两个框框都是有重复记录的,红框和绿框都只需要留下一条, ...

  8. Flutter 中那么多组件,难道要都学一遍?

    在 Flutter 中一切皆是 组件,仅仅 Widget 的子类和间接子类就有 350 多个,整理的 Flutter组件继承关系图 可以帮助大家更好的理解学习 Flutter,回归正题,如此多的组件到 ...

  9. JQuery文件上传插件JQuery.upload.js的用法简介

    JQuery文件上传插件,这个插件很小,用法很简单,效果却很棒.注意:JQuery版本要求1.8及以上,大家执行如果没效果,则检查JQuery版本,如果是1.8及以上,则该插件源码中的.size()需 ...

  10. C语言学习笔记——特别篇(VScode安装使用)

    B站有同步教学视频 参考博文: https://www.cnblogs.com/czlhxm/p/11794743.html 注意事项: 请在英文目录下运行!!! VScode下载链接: https: ...