MySQL优化-索引

Posted zhouwanchun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL优化-索引相关的知识,希望对你有一定的参考价值。

mysql优化-索引

二分查找
拆半查找,binary search
一种在有序数组中查找某一特定元素的搜索算法。
二分查找法的优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,二分查找方法适用于不经常变动而查找频繁的有序列表。


二叉树
二叉树的每个节点至多只有二棵子树(不存在度大于2的节点),二叉树的子树有左右有序之分,次序不能颠倒。


平衡树,平衡二叉树,self-balancing binary search tree
改进的二叉查找树。一般的二叉查找树的查询复杂度是跟目标节点到树根的距离(即深度)有关,因此当节点的深度普遍较大时,查询的均摊复杂度会上升,为了更高效的查询,有了平衡树。
平衡二叉树的特点:
    A.它是一棵空树或其左右两个子树的高度差的绝对值不超过1,且左右两个子树也是平衡二叉树。
    B.不平衡树会通过自旋,变成平衡树。
    C.平衡树和二叉查找树最大的区别:前者是平衡的,后者未必。

非平衡二叉树演变成平衡二叉树


B树,balanced tree
又称B-树、B_树
B树,一个节点可以拥有多于2个子节点的多叉查找树。
适合大量数据的读写操作,普遍运用在数据库和文件系统。
一棵m阶(比如m=4阶)的B树满足下列条件:
    A.树中每个节点至多有m个(4个)子节点;
    B.除根节点和叶子节点外,其他每个节点至少有m/2个(2个)子节点;
    C.若根节点不是叶子节点,则至少有2个子节点;
    D.所有叶子节点都出现在同一层,叶子节点不包括任何键值信息;
    E.有k个子节点的非叶子节点恰好包含有k-1个键值(索引节点)。

B+树,B+tree
B+树是B树的变体,也是多路搜索树,其定义基本与B树相同。除了:
    1.有n棵子树的节点(node)中含有n-1个关键字(key),每个关键字不保存数据,只用来索引,所有数据都保存在叶子节点。
    2.所有的叶子节点中包含了全部关键字的信息,及指向这些关键字记录的指针,且叶子节点本身以关键字的大小自小而大顺序链接。
    3.所有的非叶子节点可以看成是索引部分,节点中仅含其子树(根节点)中的最大(或最小)关键字。
    4.在MySQL中,为了方便,直接写成Btree。

[dba@localhost:mysql.sock] [(none)]> show index from app01.t1G
*************************** 1. row ***************************
        Table: t1
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
1 row in set (0.00 sec)

假设有个表,只有一个int列,且设置为主键,无其他更多列。
B+树中,每个非叶子节点开销:6B(row header固定开销)+4B(主键为int类型)+4B(指向叶子节点的指针开销)
每行数据开销:14B+6B(DB_TRX_ID)+7B(DB_ROLL_PTR)=27B
每个非叶子节点page存储约(16*1024-128page header)/14=1161行记录
每个叶子节点page存储约(16*1024-128page header)/27=600行记录
因此,一个三层高的B+树,约可存储记录1161*1161*600=8亿记录
这样看,B+树是不是的确很高效呢?


哈希索引,hash index
建立在哈希表的基础上,它只对使用了索引中的每个值的精确查找有用。
对于每一行,存储引擎计算出了被索引的哈希码(hash code),它是一个较小的值,并且有可能和其他行的哈希码相同。
把哈希码保存在索引中,并且保存了一个指向哈希表的每一行的指针。
也叫散列索引。

大量唯一值的等值查询,hash索引效率通常比B+tree高。
hash索引不支持模糊查找。
hash索引不支持联合索引中的最左匹配规则。
hash索引不支持排序。
hash索引不支持范围查询。
hash索引只能显示应用于heap/memory/ndb表。


什么是索引
    相当于书的目录,用于快速检索。
优点:
    提高数据检索效率。
    提高表间的join效率。
    利用唯一性索引,保证数据的唯一性。
    提高排序和分组效率。
缺点:
    消耗更多的物理存储。
    数据变更时,索引也需要更新,降低更新效率。

索引使用建议
哪种情况下应该创建索引
    A.经常检索的列
    B.经常用于表连接的列
    C.经常排序/分组的列

索引不使用建议
    A.基数很低的列(基数低就是重复数据太多的列)
    B.更新频繁但检索不频繁的列
    C.blob/text等长内容列
    D.很少用于检索的列


聚集索引,clustered index
聚集索引是一种索引的组织方式,该索引中键值的逻辑顺序决定了表数据行的物理顺序。
每张表只能建一个聚集索引,除了TokuDB引擎。
InnoDB中,聚集索引即表,表即聚集索引。
MyISAM是堆组织表,没有聚集索引的概念。

聚集索引优先选择列:
    1.int/bigint
    2.数据连续(单调顺序)递增/自增

不建议的聚集索引:
    1.修改频繁的列
    2.新增数据太过离散随机。

主键索引,primary key
主键由表中的一个或多个列组成,用于唯一地标识表中的某一条记录。
在表引用中,主键在一个表中引用来自于另一个表中的特定记录(外键foreign key应用)
保证数据的完整性。
加快数据的操作速度。
主键值不能重复,也不能包含null。

主键选择建议:
    1.对业务透明,无意义,免受业务变化的影响。
    2.很少修改和删除。
    3.最好是自增的。例如int/bigint
    4.不要具有动态属性,例如随机值。

InnoDB聚集索引选择次序原则:
    1.显式声明的主键。
    2.第一个not nullable的唯一索引。
    3.DB_ROW_ID(实例级,6 bytes)

DB_ROW_ID vs _rowid
当InnoDB表选择int类型做聚集索引时,如果符合下面两种情况,可以用_rowid来指代(alias、别名)相应的聚集索引列。
    显式定义的主键,int类型,单列。
    没有显式定义的主键,选择NOT NULL UK INT列作为聚集索引,且单列。
    不支持多列聚集索引。
    不是DB_ROW_ID的概念。
    也不是类似Oracle里的rownumber概念。

主键索引一定是聚集索引。
但是聚集索引不一定是主键。
InnoDB表一定有聚集索引。

InnoDB主键特点:
    1.索引定义时,不管有无显式包含主键,实际都会存储主键值。
    2.在5.6.9后,优化器已能自动识别索引末尾的主键值(index extensions),在这之前则需要显式加上主键列才可以被识别。
        where c1 = ? and pk = ?
        where c1 = ? order by pk

为什么主键建议用自增id?
    主键值被更新时,聚集索引也跟着更新,row data会发生物理迁移,I/O代价更高。
    主键有业务用途时,在高可用主从切换过程中,可能主从节点主键id会不一致,或者有冲突,这时候处理起来很麻烦。
    某InnoDB表,没有自增列主键,重整表空间回收碎片后从13G收缩到9G。

本节小结:
InnoDB是索引组织表,InnoDB的聚集索引就是整张表。
聚集索引非常重要,对表的读写性能影响很大。
最好是选择没有业务用途的自增id作为主键。
旧业务中已有业务属性的主键可以转成唯一索引,新增自增id主键。
看到用char/uuid类型做主键的,坚决尽早改掉。
Index Extensions特性很好用,可以发挥索引中聚集索引列的用处。


唯一索引,unique key
不允许具有索引值相同的行,从而禁止重复的索引或兼职。
严格意义上讲,应该叫做唯一约束。
在唯一约束上,和主键一样(以MyISAM引擎为代表)
其他不同的方面:
    1.唯一索引允许有空值(null)。
    2.一个表只能有一个主键,但可以有多个唯一索引。
    3.InnoDB表中主键必须时聚集索引,但聚集索引可能不是主键。
    4.唯一索引约束可临时禁用,但主键不行。


联合索引,combined indexes, multiple-column indexes
多列组成,所以也叫多列索引。
适合where条件中的多列组合。
有时候,还可以用于避免回表(覆盖索引)
MySQL还不支持多列不同排序规则(MySQL 8.0起支持)
联合索引建议:
    1.where条件中,经常同时出现的列放在联合索引中。
    2.把选择性(过滤性/基数)大的列放在联合索引的最左边。


覆盖索引,covering indexes
通过索引数据结构,即可直接返回数据,不需要回表。
执行计划中,Extra列会显示关键字using index.
回表:读取的列不在索引中,需要回到表找到整条记录取出相应的列。


前缀索引,prefix indexes
使用前缀索引的原因:
    1.char/varchar太长全部做索引的话,效率太差,存在浪费。
    2.或者blob/text类型不能整列作为索引列,只能使用前缀索引。
    3.一般索引长度建议不超过100字节(key_len不超过100)。
前缀索引怎么创建:
    1.先统计索引列的平均长度。
    2.创建的前缀索引能满足80%~90%覆盖度就基本够了。
前缀索引的缺点:
    1.无法使用覆盖索引特性,必须回表。
    2.无法利用前缀索引完成排序。(无法用于消除排序)


外键约束(foreign key constraints)
用于确保存储在外键表中的数据一致性,完整性。
本表列须与外键列类型相同(外键须是外表主键)。
外键选择原则:
    1.为关联字段创建外键。
    2.所有的键都必须唯一。
    3.避免使用复合键。
    4.外键总是关联唯一的键字段。
外键容易造成更多的行锁、死锁,不建议使用,而用代码来控制逻辑完整性。


全文索引(fulltext)
5.6以前fulltext只支持MyISAM引擎。
5.6以后,也开始支持InnoDB引擎。
5.7以前,中文支持很差。
优先使用shpinx/lucene/solr等实现中文检索。

本节小结:
可以将旧系统中,有业务用途的主键转为唯一索引。
联合索引选择要有取舍,业务上也尽量做妥协,一般不建议超过5个。
合理利用覆盖索引特性,减少回表请求。
字符串列默认采用前缀索引。


InnoDB索引长度
索引最大长度767 bytes
启用innodb_large_prefix = 1,最大索引长度增加到3072 bytes,但只针对dynamic、compressed格式管用。
对于redundant、compact格式,最大索引长度还是767 bytes。
最大排序长度默认是1024 (max_sort_length)。
MyISAM表索引最大长度时1000 bytes。
MySQL 8.0开始,没有了file format选项,只有row format概念。
此外,从8.0开始,废除选项innodb_large_prefix。因此对dynamic、compressed两种行格式的表来说,最大长度直接就是3072 bytes。


MySQL 8.0索引新特性:
倒序索引
create table t_01 (
    id int unsigned not null auto_increment,
    u1 int unsigned not null default 0,
    u2 int unsigned not null default 0,
    c3 varchar(64)  not null default ‘‘,
    c4 varchar(64)  not null default ‘‘,
    primary key (id),
    key u1 (u1 desc, u2)
) engine=innodb charset=utf8mb4;


不可见索引
    设置invisible
        alter table t1 alter index u1 invisible;
        show create table t1G
        show index from t1;
        执行SQL时即便force index索引也不可用。


函数索引、表达式索引
    8.0.13开始支持函数索引,表达式索引。
    本质上是生成列/虚拟列(generated column)。


跳跃索引扫描,index skip scan
    8.0.13开始支持skip index scan
    执行计划的Extra会显示Using index for skip scan
    针对单表,不能是多表join
    SQL中不能有group bydistinct
    多列联合索引中,第一列的唯一值很少,且在where条件中未被用到。
    set optimizer_switch=skip_scan=off; 关闭


索引并行读
    从8.0.14开始,支持主键索引并行读。
    不支持辅助索引上的并行读。
    使得check table的速度更快。
    新增选项innodb_parallel_read_threads
    innodb_parallel_read_threads=4(默认),check table耗时减少20%。


多值索引(multi-valued index)
    主要用于json列,有多个值的场景。
    支持唯一索引。
    是functional index的一种变体,本质上也是基于虚拟列。
    一个多值索引上,只能有一个多值列。
    数组中的所有数据类型必须一致,只支持decimal/integer/datetime/varchar/char等类型。
    不支持:倒序索引、online DDL、外键、前缀索引。
    在B+树中,分成多条记录存储,因此DML时,可能会对应多次操作。


MySQL索引管理使用
创建/删除索引
    1.alter table add index idx(c1) using btree;
    2.create index idx on t(c1) using btree;
    3.create table时也可以顺便创建索引。
    4.alter table t drop index idx;
    5.drop index idx on t;


冗余索引
    根据最左匹配原则,一个索引是另一个索引的子集。
    index k1(a,b,c)
    index k2(a,b)
    一般认为,k2是k1的冗余索引。
    可使用工具pt-duplicate-key-checker检查/schema_redundant_indexes.
    重复索引也是冗余索引的一种,可以直接放心删除。
    查看冗余索引
        select * from sys.schema_redundant_indexes where table_schema=worldG
        sql_drop_index: ...


无用索引
    几乎从未被使用过的索引。
    pt-index-usage检查低利用率索引,提供删除建议/schema_unused_indexes.
    查看无用索引
        select * from sys.schema_unused_indexes where object_schema = yejr;


使用索引
    1.让MySQL自动选择
        select ... from t where ...
    2.自主建议索引
        select ... from t use index(k1) where ...
    3.强制hint索引
        select ... from t force index(k1) where ...
        select ... from t force index(k1,k2) where ...


查看每个索引利用率
select index_name,rows_selected,rows_inserted,rows_updated,rows_deleted
from sys.schema_index_statistics
where table_schema=world and table_name=city;

db01 [/usr/local/mysql/bin] 2020-01-29 16:06:48
root@pts/1 # ll -h /usr/local/mysql/bin/myisamchk     
-rwxr-xr-x 1 mysql mysql 14M Sep 27 16:45 /usr/local/mysql/bin/myisamchk

innodb是索引组织表,innodb的聚集索引就是整张表。
聚集索引非常重要,对表的读写性能影响很大。
最好是选择没有业务用途的自增id作为主键。
旧业务中已有业务属性的主键可以转成唯一索引,新增自增id主键。
看到用char/uuid类型做主键的,坚决尽早改掉。
Index Extensions特性很好用,可以发挥索引中聚集索引列的用处。


MySQL 8.0直方图
MySQL 8.0开始支持,索引之外的数据分布统计信息可选项。
支持两种直方图模式:
    等宽(singleton),每个桶只有一个值,保存该值和累计的频率。
    等高(equi-height),每个桶保存上下界、累积频率以及不同值的个数。
    指令analyze table [table] update/drop histograms
    统计信息持久化存储在mysql.column_statistics表中(不可见表)
    可以从I_S.column_statistics中引用查看。
        select json_pretty(histogram) from information_schema.column_statisticsG
统计结果中的sampling-rate表示采样比例,越高消耗内存越多。
histogram_generation_max_mem_size可限制内存使用上限,默认20MB。
有了索引为什么还需要直方图?
    索引维护代价高,而且要保持(近乎实时)更新。
    而直方图更新代价更低,由用户按需更新,可自行定义更新时间。


当一个表从未被访问过时,从sys schema查询不到它的索引使用情况,这时候都有什么办法呢?
执行ananlyze table;


MySQL如何维护索引统计信息?
1.索引统计信息
show index from table
select * from I_S.statistics
mysql.innodb_index_stats

2.
innodb_stats_auto_recalc
默认启用,当修改数据量>10%时,自动更新统计信息。

innodb_stats_persistent
统计信息持久化存储,默认启用

innodb_stats_persistent_sample_pages
统计信息持久化存储时,每次采集20个page.

innodb_stats_on_metadata
默认禁用,访问meta data时更新统计信息。

innodb_stats_persistent=0
统计信息不持久化,每次动态采集,存储在内存中,重启失效(需要重新统计),不推荐。

innodb_stats_transient_sample_pages
动态采集page,默认8个。

每个表设定统计模式
create/alter table ... stats_persistent=1,stats_auto_recalc=1,stats_sample_spages=200;

本节小结:
默认的索引统计策略即可,基本无需调整。
当发现个别表索引选择不准确或统计信息不准确时,再具体分析。
一般可以通过加大sample size提高准确性。
如果数据倾斜的太厉害,可能需要重建整张表。


mysql -A
-A = --no-auto-rehash

optimize和analyze区别?


执行计划中的key_len如何解读,它有什么作用?
explain中的key_len
正常的,等于索引列字节长度。
字符串类型需要同时考虑字符集因素。
若允许null,再+1B
变长类型(varchar),再+2B

本节小结:
利用key_len判断索引效率。
一般建议key_len不超过100字节。
利用key_len判断、佐证SQL的执行计划。

[dba@localhost:mysql.sock] [app01]> explain format=json select id,c1,c2 from t1 where id=1G
*************************** 1. row ***************************
EXPLAIN: {
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "1.00"
    },
    "table": {
      "table_name": "t1",
      "access_type": "const",
      "possible_keys": [
        "PRIMARY"
      ],
      "key": "PRIMARY",
      "used_key_parts": [
        "id"
      ],
      "key_length": "4",
      "ref": [
        "const"
      ],
      "rows_examined_per_scan": 1,
      "rows_produced_per_join": 1,
      "filtered": "100.00",
      "cost_info": {
        "read_cost": "0.00",
        "eval_cost": "0.20",
        "prefix_cost": "0.00",
        "data_read_per_join": "272"
      },
      "used_columns": [
        "id",
        "c1",
        "c2"
      ]
    }
  }
}
1 row in set, 1 warning (0.00 sec)

索引如何提高SQL效率的:
1.提高数据检索效率。
2.提高聚合函数效率。sum(), avg(), count()
3.提高排序效率,order by asc/desc
4.有时候可以避免回表。
5.减少多表关联时扫描行数。
6.唯一、外键索引还可以作为辅助约束。
7.列定义为default null时,null值也会有索引,存放在索引树的最前端部分,因此尽量不要定义允许null。

索引用不上?
1.通过索引扫描的记录数超过20%~30%,可能会变成全表扫描。
2.联合索引中,第一个索引列使用范围查询(这时用到部分索引)。
3.联合索引中,第一个查询条件不是最左索引列。
4.模糊查询条件列最左以通配符%开始。
5.heap表使用hash索引时,使用范围检索或者order by.
6.多表关联时,排序字段不属于驱动表,无法利用索引完成排序。
7.两个独立索引,其中一个用于检索,一个用于排序(只能用到一个)。
8.join查询时,关联列数据类型(以及字符集/校验集)不一致也会导致索引不可用。

30%原则:
如果扫描比例超过20%~30%, 则无法使用索引,而改成全表扫描。
这是一个大概的比例,不是严格规则。
5.7起完善CBO规则,这个比例会有更大变化。

索引合并(index merge)是怎么回事,有什么作用?

索引列上最好是不允许NULL.
    索引查找、统计、值比较、会更加复杂。
    在B+树里,所有null值放在最左边,增加搜索代价。
    主从复制环境中,表中有uK(含NULL),也有PK及其他普通SK。有个删除的SQL,在主库执行时,选择普通SK效率更高,但是在从库时,却选择了含NULL的UK,效率极低,造成主从延迟严重。

本节小结:
牢记几种索引可能"不可用"或者效率低的场景。
虽然有index skip sacn特性,但也要牢记索引的最左匹配原则。
index merge可以提高多个独立索引的利用率,不过要取决于优化器的选择。
索引列上最好是不允许null。


不管是小表还是大表,需要时还是乖乖加上索引吧,否则有可能它就是瓶颈。


索引建议:
一个索引里包含的列数,最好不要超过5个。
一个表的索引数,也不要太多,一般也不要超过5个。
联合索引中,把过滤性高(基数大)的列放在左边。
需要函数索引?使用MySQL 5.7的虚拟列,或升级到MySQL 8.0。
需要表达式索引?使用MySQL 5.7的虚拟列,或升级到MySQL 8.0。
需要倒序索引?升级到MySQL 8.0。
需要临时禁用索引?升级到MySQL 8.0。
需要位图(bitmap)索引?抱歉,这个没有。
hash join在8.0.18就出来了,效果极好。
使用like关键字时,前置%会导致索引失效。
使用null值会被自动从索引中排除,索引一般不会建立在有空值的列上。
使用or关键字时,or左右字段如果存在一个没有索引,有索引字段也会失效。
使用!=操作字符时,将放弃使用索引。因为范围不确定,使用索引效率不高,会被引擎自动改为全表扫描。
不要在索引字段进行运算。
在使用复合索引时,最左前缀原则,查询时必须使用索引的第一个字段,否则索引失效,并且应尽量让字段顺序与索引顺序一致。
避免隐式转换,定义的数据类型与传入的数据类型保持一致。

 

以上是关于MySQL优化-索引的主要内容,如果未能解决你的问题,请参考以下文章

mysql有几种索引类型?使用索引时都有那些地方要注意?sql优化原则是啥?

MYSQL优化 学习笔记

MySql知识体系总结(SQL优化篇)

MySQL 千万 级数据量根据(索引)优化 查询 速度

MySQL 千万 级数据量根据(索引)优化 查询 速度

从数据库代码和服务器对PHP网站Mysql做性能优化