InnoDB索引优化

InnoDB索引优化 MySQL架构
InnoDB索引优化
文章图片
MySQL可以分为 Server 层和存储引擎层。
Server 层包括连接器、查询缓存、解析器、优化器和执行器等,涵盖了 MySQL 大多数核心服务功能。
存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Mermory 等多个存储引擎。
MySQL如何工作
连接器
当你在客户端输入 mysql –u $user –p $pwd 连接 mysql 的时候,接待你的就是连接器。连接器的作用就是和客户端建立连接、获取权限、维持和管理连接。
查询缓存
建立连接后,就可以执行select语句了。首先MySQL会去查看查询缓存,看下之前是否已经执行过这条查询语句。如果命中缓存,就直接返回。否则就扔给解析器。
解析器
MySQL需要知道你想做什么,因此我们就来到了解析器。解析器会做词法分析和语法分析。词法分析主要是分析每个词的含义,语法分析会判断你写的 SQL 语句是否满足 SQL 语法。
优化器
经过解析器,MySQL就知道你想做什么了。但是在开始执行之前,还需要经过优化器的处理。优化器会优化你的SQL语句。生成最终的执行方案 (execution plan)。然后进入执行器阶段。
执行器
执行器首先会判断你对这张表有没有相应的权限。如果没有,就报错。如果有,就调用相应的存储引擎接口,执行语句。然后将结果集返回给客户端。
存储引擎
数据的存储和提取是由存储引擎负责的,它负责和文件系统打交道。
MySQL 的存储引擎是插件式的。不同的存储引擎支持不同的特性。
选择合适的存储引擎对应用非常重要 。

# 查看MySQL支持哪些存储引擎 SHOW ENGINES; # 查看默认存储引擎 SHOW VARIABLES LIKE ‘%storage_engine%’; # 查看某张表的存储引擎 SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA='$db' AND TABLE_NAME='$table';

MyISAM
MySQL 5.5 之前默认的存储引擎。
特点:
a. 查询速度很快
b. 支持表锁
c. 支持全文索引
d. 不支持事务
使用 MyISAM 存储表,会生成三个文件.
.frm # 存储表结构,是任何存储引擎都有的
.myd # 存放数据
.myi # 存放索引
索引和数据是分开存放的,这样的索引叫非聚集索引。
*InnoDB
MySQL 5.5 以及以后版本默认的存储引擎。没有特殊应用,Oracle官方推荐使用InnoDB 引擎。
特点:
a. 支持事务
b. 支持行锁
c. 支持MVCC
d. 支持崩溃恢复
e. 支持外键一致性约束
使用 InnoDB 存储表,会生成两个文件.
.frm # 存储表结构,是任何存储引擎都有的
.ibd # 存放数据和索引
索引和数据存放在一起,这样的索引叫聚集索引。
Memory
特点:
a. 所有数据都存放在内存中,因此数据库重启后会丢失
b. 支持表锁
c. 支持Hash和BTree索引
d. 不支持Blob和Text字段
Memory由于数据都放在内存中,以及支持Hash索引,它的查询速度是最快的。
一般使用 Memory 存放临时表。
临时表:在单个连接中可见,当连接断开时,临时表也将不复存在。
磁盘IO原理
InnoDB索引优化
文章图片
磁盘上的数据可以用一个三维地址标识: 柱面号, 盘号, 块号(磁道上的扇区)
读/写数据的步骤:
  1. 移动磁头到指定的柱面号,这个过程被称为定位或查找。
    由于是机械移动, 这部分耗时最高, 最大可达 0.1s.
  2. 根据盘面号确定从哪个磁盘读取数据
  3. 盘组开始旋转,将指定的块号移动到读/写头下
    磁盘旋转的速度很快, 一般为7200rpm。旋转一圈大约需要 0.0083s.
  4. 读写数据
    数据通过系统总线传送到内存。一般传输一个字节大概需要 0.02us.
    读写 4KB 大约需要 80us.
磁盘读取数据是以盘块(block)为单位的, 一般为4KB。位于同一盘块的所有数据会被一次性全部读取出来。磁盘IO的代价主要花费在第 1 步。
结论:IO的速度是很慢很慢的,我们应该尽量少地读写磁盘!
数据页格式
页是 InnoDB 磁盘管理的最小单位。在 InnoDB 存储引擎中, 页默认大小为16KB。
可以通过参数 innodb_page_size 将页的大小设置为 4K、8K 和 16K。
InnoDB 每次至少会将 1 个页的数据从磁盘读取到内存,每次至少也会将 1 个页的数据从内存写到磁盘。
在InnoDB存储引擎中,有很多种页类型。其中最重要的是数据页,也叫 B-tree Node。里面存储了索引和数据的信息。
InnoDB索引优化
文章图片
File Header
主要存储表空间相关信息
Page Header
主要存储数据页的元信息
Infimum + Supremum Records
每个数据页中有两个虚拟行记录用来限定记录的边界,infimum record 是数据页上最小的记录,supremum record 是数据页上最大的记录。
User Records
用户数据,实际存储的行记录。
Free Space
空闲空间。
Page Directory
页目录,存放了记录的相对位置。
File Trailer
位于数据页的最后,用来检测页是否完整地写入磁盘。
InnoDB索引优化
文章图片
行记录是用链表形式组织的,最小最大记录相当于两个哨兵。Page Directory是一个数组,里面包含很多指向记录的指针(又叫 Slot),S0指向最小记录的链表, Sn指向最大记录的链表。S1 ~ Sn-1 的每条链的长度范围为 [4, 8]。
InnoDB索引优化
文章图片
File Header 里面有两个字段:FIL_PAGE_PREV 和 FIL_PAGE_NEXT 用来表示上一个页和下一个页,因此,页与页之间是用双链表链接的。页内的记录是由单链表从大到小依次链接的。
索引
索引:在 MySQL 中也叫做键 (key),是存储引擎用于快速找到记录的一种数据结构。
哪些数据结构可以作为索引?
有序数组
哈希表 (hash索引)
平衡二叉树
B树
B+树
能够快速查找的数据结构都可以作为索引。
能够讲讲作为索引时,各个数据结构的优缺点吗?
  • 有序数组:等值查找,区间查找都比较块。但是增删需要移动大量的元素,会很慢。
  • 哈希表:增加、删除,等值查询都很快。但是区间查找,排序等操作会很慢。
  • BST索引:
    不管是,增加,删除,还是等值查找,时间复杂度都是O(logn),n 是数据页的数目。
    并且支持范围查找。
    但是当数据量比较大,页的数目很多时,二叉树的高度会比较高。IO 的次数会比较多。
    查找效率低。
  • B树索引:
    B 树,它是一颗多路平衡查找树。我们描述一颗 B 树时需要指定它的阶数,阶数表示了一个结点最多有多少个孩子,一般用字母m表示。一颗m阶的B树定义如下:
    1. 除根结点外,每个结点最多有m-1个关键字, 至少有 ceil(m/2) – 1个关键字。
    2. 根结点可以只有1个关键字,最多只有 m-1 关键字。
    3. 每个结点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。
    4. 所有叶子结点都位于同一层,或者说根结点到每个叶子结点的路径长度都相同。
InnoDB索引优化
文章图片
? B树索引和数据是一起存放的。
? 缺点:a. 一行记录的数据很多时,比如1K。b. 范围查找。
  • B+树索引
    B+ 树是在B树上做了些改进。一棵 m 阶的 B+ 树定义如下:
  1. B+树包含2种类型的结点:内部结点 (也称索引结点) 和叶子结点。根结点即可以是内部结点,也可以是叶子结点。根结点的关键字个数可以只有1个。
  2. B+树与B树最大的不同是内部结点不保存数据,只保存关键字,所有数据 (记录) 都保存在叶子结点中。
  3. m阶B+树表示了内部结点最多有m个关键字。至少有 ceiling(m/2)个关键字。
  4. 内部结点中的key都按照从小到大的顺序排列,叶子结点中的记录也按照key的大小排列。
  5. 每个叶子结点都存有相邻叶子结点的指针,叶子结点依关键字大小依次链接。
B+ 树由于内部节点只存储索引,因而每个节点可以有很多很多关键字。这样不管一行记录的数据大小,都可以保证树的高度很低。
而且由于叶子节点是由链表按大小依次连接的 (在InnoDB 中是双链表)。范围查找的时候,也可以避免过多的IO次数。
InnoDB索引优化
文章图片

索引的好处与坏处
好处
提高数据检索的效率,降低数据库的IO成本。
a. 查找
b. 排序
c. 分组
d. 表的连接
坏处
a. 占用额外的空间。有时候索引占用的空间甚至比数据占用的空间还多。
b. 虽然索引大大提高了查询的速度,但同时也降低了更新表的速度。因为数据库不仅仅要更新数据,还要更新对应的索引信息。
PS:索引不是越多越好!索引太多,应用程序的性能可能会受到影响; 索引太少,查询速度会变慢。我们应该建立合适的索引,找到一个平衡点!
InnoDB索引
  • B+ 树索引
    InnoDB 中的 B+ 树索引又可以分为聚集索引 (clustered index) 和 辅助索引 (secondary index)。它们之间的不同在于,聚集索引的叶子节点存储的是一整行记录的信息,而辅助索引的叶子节点只存放部分信息 (关键字和主键)。
  • 全文索引
    全文索引是搜索关键字的,类似于搜索引擎。
  • 哈希索引
    哈希索引是自适应的,我们不能自己创建。
B+树索引
  • 聚集索引
    聚集索引就是按照每张表的主键构建一棵 B+ 树,同时叶子节点中存放的是整张表的行记录数据。
    聚集索引的叶子节点其实就是我们前面讲过的数据页。
    在InnoDB中, 记录只能存放在聚集索引中,所以每张表有且只有一个聚集索引。在大多数情况下,查询优化器倾向于采用聚集索引。
    Q:如果一张表中没有主键,该怎么办呢?
    找第一个定义的唯一键去构建聚集索引。
    Q:如果没有定义唯一键,又该怎么办呢?
    Innodb会提供隐藏的主键,根据隐藏的主键去构建聚集索引。
    PS:所以,建议创建表的时候一定要创建主键。
  • 辅助索引
    对于辅助索引,叶子节点不包含行记录的全部数据,叶子节点除了包含键以外,还包含聚集索引的键,也就是主键的信息。
    辅助索引的存在并不影响数据在聚集索引中的组织,因此每张表可以有多个辅助索引。
  • Q:有一张公民表,有身份证号码、姓名、年龄、性别…等信息,我们知道公民的身份证号码是唯一并且非空的,那么用身份证号码当主键好吗?
    不好,身份证是以字符串形式存储,辅助索引每一个叶子节点除了包含键还有主键,造成数据冗余
语法
创建:
创建表的时候指定。会自动给 primary key 和 unique 创建索引。CREATE [UNIQUE] INDEX 索引名 ON 表名(字段列表); ALTER 表名 ADD [UNIQUE] INDEX 索引名 (字段列表);

删除:
DROP INDEX 索引名 ON 表名;

查看:
SHOW INDEX FROM 表名;

回表
【InnoDB索引优化】当通过辅助索引来寻找数据的时候,InnoDB会遍历辅助索引,并通过辅助索引的叶子节点,获取主键。然后再通过聚集索引来找到一个完整的行记录。这个过程我们称之为回表(遍历过程:辅助索引—>聚集索引)。
如果一个辅助索引的高度为3,聚集索引的高度为3。那么我们需要6次IO操作,才可以访问最终的数据。应该尽量避免回表!
Explain
查询优化器:通过计算分析系统收集的统计信息,提供它认为最优的执行计划(execution plan)。
explain:查看这个执行计划的信息。
语法:explain + select 语句
例子:explain select * from t;
我们发现explain的结果有以下列的信息:
id, select_type, table, partitions, type, possible_keys, key, ken_len, ref, rows, filtered, Extra
  • id: 每个 select 子句的标识。
  • select_type: select 语句的类型。simple, primary, union, subquery, derived。
  • table:显示这一行的数据是关于哪张表的。
  • partitions: 匹配的分表。
  • type: 连接类型,又叫 “访问类型”。
    常用类型有:ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能越来越好)
  • possible_keys:可以选择的索引。
  • key: 实际选择的索引。
  • key_len: 使用索引的长度 (以字节为单位)。
  • ref: 与索引比较的字段
  • rows: 大概要检索的行数
  • Extra: 额外信息。
    • using filesort: MySQL中无法利用索引完成的排序操作称为 ”文件排序”,常见于排序和分组查询。
    • using temporary: 表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询。
    • using filesort 和 using temporary, 这两项比较耗时, 需要特别小心。
联合索引
联合索引是指对表的多个列进行索引。前面的讨论的都是只对表上的一个列进行索引。类比于多个字段作为主键。
联合索引的创建方法和单个索引创建的方法一样,不同之处仅在于有多个索引列。
联合索引有什么好处?
  • 减少开销(每多一个索引,都会增加磁盘空间的开销)
  • 键是已经排好序的(查询优化器选择联合索引,因为记录已经根据键排好序)
  • 可能会覆盖索引,直接通过遍历索引取得数据,无需回表
最左前缀原则
B+ 树这种数据结构,可以利用索引的 ”最左前缀” 来定位记录。最左前缀原则指索引项是按照索引定义里面出现的字段顺序排序的,只要查询的是索引项的最左字段(相同则查询下一字段),就可以利用联合索引来加速检索。这里需要注意的是,如果直接查询后面的字段,索引根据最左字段排序,但实际上后面的字段是无序的
覆盖索引
InnoDB 存储引擎支持覆盖索引 (covering index),即从辅助索引中就可以得到要查询的信息,而不需要回表。
使用覆盖索引的好处是,覆盖索引不包含整行记录的信息,故其大小一般情况下远小于聚集索引,因此可以减少大量的 IO 操作。
对于辅助索引而言,其叶子节点包含主键信息。
何时创建索引
  • 主键和唯一键会自动创建索引,外键要求必须是primary key 或者 unique。
  • 频繁作为查询条件的字段应该创建索引。
  • 与其他表关联的字段应该创建索引。
  • 查询中用于经常用于排序的字段。
  • 查询中经常用于分组的字段。
何时不创建索引
  • 表的记录太少
  • 经常更新的表
  • 数据字段中包含太多的重复值,比如国籍,性别等字段。
索引失效的情况
  • 对索引列进行运算
  • 对索引进行不等于判断
  • 范围查找过大
实践策略
  • 尽量使用覆盖索引, 比如尽量不要使用 select *。
  • 尽量使用最左前缀法则
  • 不要在索引列上做运算
  • 范围查找尽量不要太大
  • 尽量不要使用不等于

    推荐阅读