09-索引优化分析
Posted liujiaqi1101
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了09-索引优化分析相关的知识,希望对你有一定的参考价值。
导致性能下降的原因
- 数据过多 → 分库分表
- 索引(单值、复合)失效 → 索引建立
- 关联查询太多 Join // 设计缺陷、不得已的需求 → SQL 优化
- 服务器调优及各个参数设置(缓冲、线程数等)→ 调整 my.cnf
RE: Join 查询
索引简介
是什么?
【官方定义】索引(Index) 是帮助 mysql 高效获取数据的数据结构。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向) 数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
下图为一种可能的索引方式示例。为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
【小结】索引本质是数据结构,可以简单理解为“排好序的快速查找数据结构”。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。
我们平时所说的索引,如果没有特别指明,都是指 B 树(多路搜索树,不一定是二叉的)。其中,聚集索引、次要索引、覆盖索引、复合索引、前缀索引、唯一索引均是默认使用 B+ 树索引,统称“索引”。当然,除了 B+ 树这种类型的索引之外,还有哈希索引(hash index) 等。
优势和劣势
优势:
- 类似图书馆建书目索引,提高数据检索的效率,降低数据库的 IO 成本。
- 通过索引对数据进行排序,降低数据排序的成本,降低了 CPU 的消耗。
劣势:
- 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
- 虽然索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行 INSERT、UPDATE、DELETE。因为
更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所
带来的键值变化后的索引信息。 - 索引只是提高效率的一个因素,如果你的 MySQL 有大数据量的表,都需要花时间研究建立最优秀的索引或优化查询语句。
MySQL 索引结构
概述
索引是在 MySQL 的存储引擎层中实现的,而不是在服务器层实现的。所以每种存储引擎的索引都不一定完全相同,
也不是所有的存储引擎都支持所有的索引类型的。MySQL 目前提供了以下 4 种索引:
- BTREE 索引: 最常见的索引类型,大部分索引都支持 B 树索引。
- HASH 索引:只有Memory引擎支持 , 使用场景简单 。
- R-tree 索引(空间索引):空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常
- 使用较少,不做特别介绍。
- Full-text(全文索引):全文索引也是 MyISAM 的一个特殊索引类型,主要用于全文索引,InnoDB 从 MySQL5.6 版本开始支持全文索引。
我们平常所说的索引,如果没有特别指明,都是指 B+ 树(多路搜索树,并不一定是二叉的)结构组织的索引。其中
聚集索引、复合索引、前缀索引、唯一索引默认都是使用 B+tree 索引,统称为 "索引"。
BTree 结构
BTree 又叫“多路平衡搜索树”,一颗 m 叉的 BTree 特性如下:
- 树中每个节点最多包含 m 个孩子。
- 除根节点与叶子节点外,每个节点至少有 [ceil(m/2)] 个孩子。
- 若根节点不是叶子节点,则至少有两个孩子。
- 所有的叶子节点都在同一层。
- 每个非叶子节点由 n 个 key 与 n+1 个指针组成,其中 [ceil(m/2)-1] <= n <= m-1。
以 5 叉 BTree 为例,key 的数量:公式推导 [ceil(m/2)-1] <= n <= m-1
。所以 2 <= n <=4,而当 n>4 时,中间节点分裂到父节点,两边节点分裂。
现以插入 C N G A H E K Q M F W L T Z D P R X Y S 数据为例,演变过程如下:
到此,该 BTREE 树就已经构建完成了, BTREE 树和二叉树相比, 查询数据的效率更高, 因为对于相同的数据量来说,BTREE 的层级结构比二叉树小,因此搜索速度快。
B+Tree 结构
B+Tree 为 BTree 的变种,B+Tree 与 BTree 的区别为:
- n 叉 B+Tree 最多含有 n 个 key,而 BTree 最多含有 n-1 个 key。
- B+Tree 的叶子节点保存所有的 key 信息,依 key 大小顺序排列。
- 所有的非叶子节点都可以看作是 key 的索引部分。
由于 B+Tree 只有叶子节点保存 key 信息,查询任何 key 都要从 root 走到叶子。所以 B+Tree 的查询效率更加稳定。
B 和 B+ 的区别:
- B 树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+ 树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
- 在 B 树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而 B+ 树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看 B 树的性能好像要比 B+ 树好,而在实际应用中却是 B+ 树的性能要好些。因为 B+ 树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比 B 树多,树高比 B 树小,这样带来的好处是减少磁盘访问次数。尽管 B+ 树找到一个记录所需的比较次数要比 B 树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中 B+ 树的性能可能还会好些,而且 B+ 树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用 B+ 树的缘故。
思考:为什么说 B+ 树比 B 树更适合实际应用中操作系统的文件索引和数据库索引?
- B+ 树的磁盘读写代价更低。B+ 树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说 IO 读写次数也就降低了。
- B+ 树的查询效率更加稳定。由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
MySQL 中的 B+Tree
MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree,提高区间访问的性能。
MySQL 中的 B+Tree 索引结构示意图:
聚簇索引与非聚簇索引
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。术语‘聚簇’表示数据行和相邻的键值聚簇的存储在一起。如下图,左侧的索引就是聚簇索引,因为数据行在磁盘的排列和索引排序保持一致。
聚簇索引的好处:
按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的 IO 操作。
聚簇索引的限制:
- 对于 MySQL 数据库目前只有 innodb 数据引擎支持聚簇索引,而 Myisam 并不支持聚簇索引。
- 由于数据物理存储排序方式只能有一种,所以每个 MySQL 的表只能有一个聚簇索引。一般情况下就是该表的主键。
- 为了充分利用聚簇索引的聚簇的特性,所以 innodb 表的主键列尽量选用有序的顺序 id,而不建议用无序的 id,比如 uuid 这种。
索引分类&语法
单值索引
即一个索引只包含单个列,一个表可以有多个单列索引。
- 随表一起建索引
CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT, customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id), KEY (customer_name) );
- 单独建单值索引
CREATE INDEX idx_customer_name ON customer(customer_name);
- 删除索引
DROP INDEX idx_customer_name ON customer;
唯一索引
索引列的值必须唯一,但允许有空值,NULL 可出现多次。
- 随表一起建索引
CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT, customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id), KEY (customer_name) UNIQUE (customer_no) );
- 单独建唯一索引
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);
- 删除索引
DROP INDEX idx_customer_no ON customer;
主键索引
设定为主键后数据库会自动建立索引,innodb 为聚簇索引。
- 随表一起建索引
CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT, customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id), );
- 单独建主键索引
ALTER TABLE customer ADD PRIMARY KEY customer(customer_no);
- 删除建主键索引
ALTER TABLE customer DROP PRIMARY KEY;
复合索引
即一个索引包含多个列。
- 随表一起建索引
CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT, customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id), KEY (customer_name) UNIQUE (customer_no) KEY (customer_no, customer_name) );
- 单独建索引
CREATE INDEX idx_no_name ON customer(customer_no, customer_name);
- 删除索引
DROP INDEX idx_no_name ON customer;
其他语法
- 创建:
CREATE [UNIQUE] INDEX [indexName] ON table_name(column)
- 删除:
DROP INDEX [indexName] ON table_name
- 查看:
SHOW INDEX FROM table_nameG
- ALTER TABLE 方式来添加数据表的索引
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list)
该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。ALTER TABLE tbl_name ADD UNIQUE index_name (column_list)
该语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。ALTER TABLE tbl_name ADD INDEX index_name (column_list)
添加普通索引,索引值可出现多次。ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list)
该语句指定了索引为 FULLTEXT,用于全文索引。
索引设计原则
索引的设计可以遵循一些已有的原则,创建索引的时候请尽量考虑符合这些原则,便于提升索引的使用效率,更高效的使用索引。
哪些情况需要创建索引?
- 对查询频次较高,且数据量比较大的表建立索引。
- 索引字段的选择,最佳候选列应当从 WHERE 子句的条件中提取,如果 WHERE 子句中的组合比较多,那么应当挑选最常用、过滤效果最好的列的组合。
- 使用唯一索引(主键自动建立唯一索引),区分度越高,使用索引的效率越高。
- 使用短索引,索引创建之后也是使用硬盘来存储的,因此提升索引访问的 I/O 效率,也可以提升总体的访问效率。假如构成索引的字段总长度比较短,那么在给定大小的存储块内可以存储更多的索引值,相应的可以有效的提升 MySQL 访问索引的 I/O 效率。
- 单键/组合索引的选择问题, 组合索引性价比更高。利用最左前缀,N 个列组合而成的组合索引,那么相当于是创建了 N 个索引,如果查询时 WHERE 子句中使用了组成该索引的前几个字段,那么这条查询 SQL 可以利用组合索引来提升查询效率。
- 查询中与其它表关联的字段,外键关系建立索引。
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度。
- 查询中统计或者分组字段。
哪些情况不要创建索引?
- 表记录太少
- 经常增删改的表或者字段。索引可以有效的提升查询数据的效率,但索引数量不是多多益善,索引越多,维护索引的代价自然也就水涨船高。对于插入、更新、删除等 DML 操作比较频繁的表来说,索引过多,会引入相当高的维护代价,降低 DML 操作的效率,增加相应操作的时间消耗。另外索引过多的话,MySQL 也会犯选择困难病,虽然最终仍然会找到一个可用的索引,但无疑提高了选择的代价。
- WHERE 条件里用不到的字段不创建索引
- 过滤性不好的字段(数据列包含许多重复的内容)不适合建索引
索引的选择性是指索引列中不同的值的数目与表中记录数的比。如果一个表中有 2000 条记录,表索引列有 1980 个不同的值,那么这个索引的选择性就是 1980/2000=0.99。一个索引的选择性越接近于 1,这个索引的效率就越高。
EXPLAIN
简述
- 作用:使用 EXPLAIN 关键字可以模拟服务层的 Optimizer 优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈。
- 通过 EXPLAIN 能获取到的信息
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被物理查询
- 语法:
EXPLAIN + SQL语句
测试环境搭建
字段说明
id
select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序。
select_type
查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
- SIMPLE: 简单的 SELECT 查询,查询中不包含子查询或者 UNION
- PRIMARY: 查询中包含任何复杂的子查询,最外层查询被标记为 PRIMARY
- DERIVED: 在 FROM 列表中包含的子查询被标记为 DERIVED(衍生),MySQL 会递归执行这些子查询,把结果放在临时表里
- SUBQUERY: 在 SELECT 或 WHERE 列表中包含子查询
- DEPENDENT SUBQUERY:在 SELECT 或 WHERE 列表中包含了子查询,子查询基于外层(比 SUBQUERY 就多个 IN)。
- UNCACHEABLE SUBQUREY:无法被缓存的子查询 (如:查询语句中有系统变量)
- UNION: 若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION;若 UNION 包含在 FROM 子句的子查询中,外层 SELECT 被标记为 DERIVED
- UNION RESULT: 从 UNION 表获取结果的 SELECT
table
对应行正在访问哪一个表,表名或者别名,可能是临时表或者 union 合并结果集。
- 如果是具体的表名,则表明从实际的物理表中获取数据,也可以是表的别名
- 表名是 derivedN 的形式,表示使用了 id 为 N 的查询产生的衍生表
- 当有 union result 的时候,表名是 union <n1,n2> 等的形式,n1,n2 表示参与 union 的 id
type
表示 MySQL 在表中找到所需行的方式,又称“访问类型”,是较为重要的一个指标。
结果值从最好到最坏依次是:null > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range(尽量保证) > index > ALL
常用的有:system > const > eq_ref > ref > range > index > all。
- const:表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引(因为只匹配一行数据,所以很快)。如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。
- system:是 const 类型的特例,当查询的表只有一行的情况下(等于系统表)是 system。
- ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而, 它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体。
- eq_ref:类似 ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配。简单来说,就是多表连接中使用 primary key 或者 unique key 作为关联条件。
- range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引,一般就是在你的 WHERE 语句中出现了
between、<、>、in
等的查询,这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。 - all:Full Table Scan,MySQL 将遍历全表以找到匹配的行。
- index:Full Index Scan,index 与 all 区别为 index 类型只遍历索引树。这通常比 all 快,因为索引文件通常比数据文件小(也就是说虽然 all 和 index 都是读全表,但 index 是从索引中读取的,而 all 是从硬盘中读的)。
- index_merge:在查询过程中需要多个索引组合使用,通常出现在有 or 的关键字的 SQL 中。
- ref_or _null
- index_subquery:利用索引来关联子查询,不再全表扫描。
- unique_subquery:该联接类型类似于 index_subquery,子查询中的唯一索引。
%key%
[possible_keys] 显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。[key] 表示实际使用的索引;如果为 NULL,则没有使用索引。
查询中若使用了覆盖索引,即索引关联字段和查询的 select 字段重叠的情况:
[key_len] 表示 !WHERE! 后面的筛选条件命中索引的长度,命中索引所关联的字段越多,长度越长。可通过该列计算查询中使用的索引的长度。
- 一般地,key_len 等于索引列类型字节长度,例如 int 类型为 4 bytes,bigint 为 8 bytes;
- 如果是字符串类型,还需要同时考虑字符集因素,例如 utf8 字符集 1 个字符占 3 个字节,gbk 字符集 1 个字符占 2 个字节
- 若该列类型定义时允许 NULL,其 key_len 还需要再加 1 bytes
- 若该列类型为变长类型,例如 VARCHAR(TEXT/BLOB 不允许整列创建索引,如果创建部分索引也被视为动态列类型),其 key_len 还需要再加 2 bytes
字符集会影响索引长度、数据的存储空间,为列选择合适的字符集;变长字段需要额外的 2 个字节,固定长度字段不需要额外的字节。而 NULL 都需要 1 个字节的额外空间,所以以前有个说法:索引字段最好不要为 NULL,因为 NULL 让统计更加复杂,并且需要额外一个字节的存储空间。
ken_len 显示的值为索引字段的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的。
ref
显示索引的哪一列被使用了,即哪些列或常量(const) 被用于查找索引列上的值。
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录需要要读取的行数。
extra
包含不适合在其他列中显示但十分重要的额外信息
- Using filesort
- Using temporary
- Using Index
- Using join buffer
- impossible where
- select tables optimized away
案例
以上是关于09-索引优化分析的主要内容,如果未能解决你的问题,请参考以下文章
MySQL数据库性能优化由浅入深(表设计慢查询SQL索引优化Explain分析Show Profile分析配置优化)