从存储结构(索引存储时保存的形式)上来划分:
从应用层次来分:
根据数据的物理顺序与键值的逻辑(索引)顺序关系:
mysql> use world;
mysql> desc city;
Key ==> 键
PRI ==> 主键索引
MUL ==> 辅助索引(单列,联合,前缀)
UNI ==> 唯一索引
mysql> show index from city\G //
Table 表名
Key_name 索引名
Column_name 列名
Cardinality 基数(选择度),越大越好
Cardinality 建立索引之前,基数如何计算?
-- 统计所有行 SELECT COUNT(district) FROM city; -- 统计所有非重复行,基数 SELECT COUNT(DISTINCT district) FROM city;
建议选择重复值在20%以内,也就是所有非重复行/所有行>0.8的行建立索引
SELECT COUNT(DISTINCT district)/COUNT(district) FROM city;
-- 单列的辅助索引
ALTER TABLE city ADD INDEX idx_name(NAME);
-- 多列的联合索引
ALTER TABLE city ADD INDEX idx_c_p(countrycode,population);
-- 唯一索引(列必须没有重复值)
ALTER TABLE city ADD UNIQUE INDEX uidx_dis(district);
-- 前缀索引
ALTER TABLE city ADD INDEX idx_dis(district(5));
-- 注意:数字列不能用作前缀索引。
ALTER TABLE city DROP INDEX idx_name;
ALTER TABLE city DROP INDEX idx_c_p;
ALTER TABLE city DROP INDEX idx_dis;
8.0新特性
invisible inxde 不可见索引,隐藏索引而不是删除
不可见的索引可以测试删除索引对查询性能的影响,而无需进行破坏性的更改,如果需要该索引,则必须撤消该更改。对于大型表,删除和重新添加索引可能会很昂贵,而使其不可见和可见则是快速的就地操作。
-- 查看索引是可见还是不可见的信息 mysql> SELECT INDEX_NAME, IS_VISIBLE -> FROM INFORMATION_SCHEMA.STATISTICS -> WHERE TABLE_SCHEMA = 'world' AND TABLE_NAME = 'city'; +-------------+------------+ | INDEX_NAME | IS_VISIBLE | +-------------+------------+ | CountryCode | YES | | idx_c_p | YES | | idx_c_p | YES | | idx_name | YES | | PRIMARY | YES | +-------------+------------+ 5 rows in set (0.00 sec) -- 设置索引不可见 mysql> ALTER TABLE city ALTER INDEX idx_c_p invisible; Query OK, 0 rows affected (0.43 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> SELECT INDEX_NAME, IS_VISIBLE FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = 'world' AND TABLE_NAME = 'city'; +-------------+------------+ | INDEX_NAME | IS_VISIBLE | +-------------+------------+ | CountryCode | YES | | idx_c_p | NO | | idx_c_p | NO | | idx_name | YES | | PRIMARY | YES | +-------------+------------+ 5 rows in set (0.01 sec) -- 设置索引可见 mysql> ALTER TABLE city ALTER INDEX idx_c_p VISIBLE; Query OK, 0 rows affected (0.06 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> SELECT INDEX_NAME, IS_VISIBLE FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = 'world' AND TABLE_NAME = 'city'; +-------------+------------+ | INDEX_NAME | IS_VISIBLE | +-------------+------------+ | CountryCode | YES | | idx_c_p | YES | | idx_c_p | YES | | idx_name | YES | | PRIMARY | YES | +-------------+------------+ 5 rows in set (0.00 sec)
mysql> CREATE DATABASE test;
mysql> USE test;
mysql> source /tmp/t100w.sql
[root@db01 ~]# mysqlslap --defaults-file=/etc/my.cnf \
--concurrency=100 --iterations=1 --create-schema='test' \
--query="select * from test.t100w where k2='MN89'" engine=innodb \
--number-of-queries=2000 -uroot -p123 -verbose
[root@db01 ~]# mysqlslap --defaults-file=/etc/my.cnf --concurrency=100 --iterations=1 --create-schema='test' --query="select * from test.t100w where k2='MN89'" engine=innodb --number-of-queries=2000 -uroot -p123 -verbose
https://dev.mysql.com/doc/refman/8.0/en/innodb-physical-structure.html
InnoDB
索引是B树数据结构。索引记录存储在其B树或R树数据结构的叶页中。索引页的默认大小为16KB。
InnoDB架构
与聚簇索引不同,二级索引通常是不唯一的,并且二级索引中的插入,删除和更新(DML操作)以相对随机的顺序发生,还可能会影响索引树中不相邻的二级索引页,产生大量随机访问I/O。
为了减少这个情况对性能的影响,暂存这些由INSERT
,UPDATE
或 DELETE
(DML操作)导致的二级索引更改至change buffer中,在系统大部分处于空闲状态或缓慢关闭的期间,运行的清除操作会定期将更新的索引页写入磁盘。
在内存中,change buffer 占用了缓冲池的一部分。在磁盘上,change buffer是系统表空间的一部分,当数据库服务器关闭时,二级索引的更改将存储在其中。
AHI:自适应HASH索引
索引的索引,为内存中的热点索引页,建立HASH索引表,能够快速找到需要的索引页地址
MySQL 8.0 默认使用的 InnoDB 存储引擎只显式支持B-Tree( 从技术上来说是B+Tree)索引,对于频繁访问的表,innodb会透明建立自适应hash索引,即在B树索引基础上建立hash索引,可以显著提高查找效率,对于客户端是透明的,不可控制的,隐式的。
基于哈希表实现,只有精确匹配索引所有列的查询才有效,对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码(hash code),并且Hash索引将所有的哈希码存储在索引中,同时在索引表中保存指向每个数据行的指针。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trhiudRO-1608293258773)(MySQL之5—索引及执行计划.assets/35a85edf8db1cb13bedd2cd7f2d0024890584bcc.jpeg)]
B-Tree(平衡多路查找树)
B-Tree数据结构始终保持排序,从而可以快速查找精确匹配(等于运算符)和范围(例如,大于,小于和BETWEEN
运算符),加快数据的访问速度,因为存储引擎不再需要进行全表扫描来获取数据,数据分布在各个节点之中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J4zcmFUI-1608293258775)(MySQL之5—索引及执行计划.assets/1b4c510fd9f9d72af2091335e7ae7c32349bbb37.jpeg)]
详解:
B-Tree是为磁盘等外存储设备设计的一种平衡查找树。因此在讲B-Tree之前先了解下磁盘的相关知识。
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小:
mysql> show variables like 'innodb_page_size';
而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率
。
B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。
一棵m阶的B-Tree有如下特性:
1.每个节点最多有m个孩子。
2.除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
3.若根节点不是叶子节点,则至少有2个孩子
4.所有叶子节点都在同一层,且不包含其它关键字信息
5.每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
6.关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
7.ki(i=1,…n)为关键字,且关键字升序排序。
8.Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)
B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个3阶的B-Tree:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ydev57n-1608293258777)(MySQL之5—索引及执行计划.assets/1637343-e131e906c2b4e270.png)]
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。
模拟查找关键字29的过程:
分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
B+Tree索引
B+Tree是B-Tree的改进版本,同时也是MySQL数据库的innodb引擎索引所采用的存储结构。数据都在叶子节点上,并且增加了顺序访问指针
,每个叶子节点都指向相邻的叶子节点的地址。进行范围查找时,B-Tree需要获取所有节点,而B+Tree只需要查找两个节点,进行遍历即可。相比之下B+Tree效率更高。
B+Tree 优点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mVmqFrAd-1608293258780)(MySQL之5—索引及执行计划.assets/8c1001e93901213ff2e8d674666362d72d2e95cd.jpeg)]
详解:
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
B+Tree相对于B-Tree有几点不同:
将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ATJXhrA4-1608293258781)(MySQL之5—索引及执行计划.assets/1637343-9fd152574ab94829.png)]
通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。
可能上面例子中只有22条数据记录,看不出B+Tree的优点,下面做一个推算:
InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节
)或BIGINT(占用8个字节)
,指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为〖10〗3)。也就是说一个深度为3的B+Tree索引可以维护103 * 10^3 * 10^3 = 10亿 条记录。
实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在24层。[mysql](https://links.jianshu.com/go?to=http%3A%2F%2Flib.csdn.net%2Fbase%2Fmysql)的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要13次磁盘I/O操作。
数据库中的B+Tree索引可以分为聚集索引(clustered index)
和辅助索引(secondary index)
。上面的B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。
B-tree:因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为扇出),指针少的情况下要保存大量数据,只能增加树的高度,导致IO操作变多,查询性能变低;
Hash:虽然可以快速定位,但是没有顺序,IO复杂度高。
二叉树:树的高度不均匀,不能自平衡,查找效率跟数据有关(树的高度),并且IO代价高。
红黑树:树的高度随着数据量增加而增加,IO代价高。
结合B+Tree的特点,自增主键是连续的,在插入过程中尽量减少页分裂,即使要进行页分裂,也只会分裂很少一部分。并且能减少数据的移动,每次插入都是插入到最后。总之就是减少分裂和移动的频率。
插入连续的数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4easmxZS-1608293258783)(MySQL之5—索引及执行计划.assets/97e1fc32d8b1c5ec14e827eb619795b6.gif)]
插入非连续的数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZkYExrAu-1608293258784)(MySQL之5—索引及执行计划.assets/404a9ec45e97550a9b1b8c5e9c6b766a.gif)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tQqI2xPV-1608293258784)(MySQL之5—索引及执行计划.assets/QQ图片20201110114919.png)]
叶子节点:先将数据排序,生成叶子节点+指向相邻叶子节点的双向指针
枝节点:保存叶子节点范围+指向叶子节点的双向指针
根节点:保存枝节点范围+指向根节点的指针
区(簇)extent:连续的64pages,默认是1M的存储空间
页page:16KB大小,Mysql中最小的IO单元
IOT(Index-Organized Table)索引组织表:表面上看是一种表,实质上其数据是以索引的形式来存放的,占用的是索引段。MySQL的表数据存储是聚集索引组织表。
聚簇索引和二级索引
构建条件
InnoDB
自动建立聚簇索引,索引列选择优先级如下:
NOT NULL
且UNIQUE
)的键,没有则向下选择构建B树结构
叶子节点:因为Mysql使用聚簇索引组织表存储数据,在存数据时,就按照索引列的顺序,存储数据到连续的数据页(16K)中,所以原表数据存储结构就是叶子节点+指向相邻叶子节点的双向指针
枝节点:存储叶子节点中索引列的范围+指向叶子节点的双向指针
根节点:存储枝节点中索引列的范围+指向根节点的指针
能优化的查询
只能优化仅使用索引列的查询,局限性很大。
加快查询原理
通过聚集索引访问行是快速的,因为索引搜索直接导致包含所有行数据的页面。如果表很大,则与使用不同于索引记录的页面存储行数据的存储组织相比,聚集索引体系结构通常可以节省磁盘I / O操作。
构建条件
需要人为按照需求创建,表中任何一个列都可以创建辅助索引列,只要名字不同即可,可以有多个
构建B树结构
叶子节点:将辅助索引列和主键提取出来,按照辅助索引列的值从小到大排序,均匀的存储到各个page中,生成叶子节点+指向相邻叶子节点的双向指针
枝节点:存储叶子节点中辅助索引列的范围+指向叶子节点的双向指针
根节点:存储枝节点中辅助索引列的范围+指向根节点的指针
能优化的查询
只能优化查询条件使用辅助索引列的查询。
会先扫描辅助索引,获得主键,再回到聚簇索引(回表查询),按照主键进行聚簇索引扫描,获得所需数据行.
最好的查询条件是同时使用辅助索引列和主键,直接可以获得所需数据行,不用回表查询,但有查询条件是主键可以直接使用聚簇索引…
加快查询原理
除聚集索引之外的所有索引都称为辅助索引。在InnoDB中,辅助索引中的每个记录都包含该行的主键列以及为辅助索引指定的列。 InnoDB使用此主键值在聚集索引中搜索行。
如果主键较长,则辅助索引将使用更多空间,因此具有短主键是有利的。
辅助索引细分
构建B树结构
叶子节点:将辅助索引列a和辅助索引列b和主键提取出来,按照辅助索引列a,b的值从小到大联合排序,存储到各个page中,生成叶子节点+指向相邻叶子节点的双向指针
枝节点:存储叶子节点中最左列(除了主键)的范围+指向叶子节点的双向指针
根节点:存储枝节点中最左列(除了主键)的范围+指向根节点的指针
能优化的查询
只能优化查询条件至少使用a条件(最左前缀的列)
的查询。
先通过a条件,扫描联合索引的根节点和枝节点,得到叶子节点范围,如果最终得到的主键是多个,就要再执行多次回表查询。如果能再通过b条件过滤,最终得到的主键更精确,理论上能减少回表的次数。回表查询是随机IO的,会大大增加查询时间。
加快查询原理(最左原则)
建立联合索引时,选择基数大(重复值少)的列作为最左列
查询条件中必须要包含最左列条件
索引树高度应当越低越好,一般维持在3-4层最佳,3层能存储2000万+行数(量级)
影响索引树高度的因素
数据量级,解决方法:
分区表:
定期归档:一般按照时间字段,定期归档到历史库中, pt-archiver
分库,分表,分布式
索引列值过长,解决方法:
业务允许,尽量选择字符长度短的列作为索引列
业务不允许,采用前缀索引.
数据类型:
变长长度字符串,使用了char
,解决方案:变长长度字符串使用 varchar
enum
使用短字符代替长字符
enum ('山东','河北','黑龙江','吉林','辽宁','陕西'......)
enum ( 1 2 3 4 5 6 )
回表是辅助索引列扫描之后,得到主键,再回到聚簇索引查询的过程
回表是随机IO,会导致IO的次数(IOPS)和量(吞吐量)增加
IOPS(Input/Output Operations Per Second):每秒的读写次数,用于衡量随机访问的性能
吞吐量:单位时间内成功地传送数据的数量(例如 200M/s)
减少回表的方法:
场景一:如果业务中出现了执行比较慢的语句,需要借助对执行计划的评估,分析优化方案。
场景二:上线新业务,语句执行前,先看执行计划,可以有效的防止性能较差的语句带来的性能问题
SQL语句没有真正运行,到优化器选择完成后停止并显示信息,获取到的是优化器选择完成,认为代价最小的执行计划.
desc SQL语句;
explain SQL语句;
mysql> desc select * from world.city where countrycode='CHN' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: city
partitions: NULL
type: ref
possible_keys: CountryCode,idx_c_p
key: CountryCode
key_len: 3
ref: const
rows: 363
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
table: city 操作的表
type: ref 操作类型(全表\索引)
possible_keys: CountryCode,idx_co_po 可能会走的索引
key: CountryCode 真正走的索引
key_len: 3 索引覆盖长度(联合索引)
rows: 363 预估需要扫描的行数
Extra: Using index condition 额外信息
从上往下效率依次升高
ALL
:全表扫描,不走索引① 查询全表
DESC SELECT * FROM world.city;
② 查询条件出现以下语句
DESC SELECT * FROM world.city WHERE countrycode LIKE '%HN';
③ 辅助索引列的查询条件出现以下语句
DESC SELECT * FROM world.city WHERE countrycode != 'CHN';
DESC SELECT * FROM world.city WHERE countrycode <> 'CHN';
DESC SELECT * FROM world.city WHERE countrycode NOT IN ('CHN','USA' );
DESC SELECT * FROM world.city WHERE Population > 100 and Population < 1000000;
DESC SELECT * FROM world.city WHERE Population between 100 and 1000000;
注意:对于聚集索引,使用例③语句,依然会走RANGE索引
DESC SELECT * FROM world.city WHERE id <> 10; DESC SELECT * FROM world.city WHERE id != 10; DESC SELECT * FROM world.city WHERE id NOT IN ('1','2' ); DESC SELECT * FROM world.city WHERE id > 10 and id < 1000; DESC SELECT * FROM world.city WHERE id between 10 and 1000;
INDEX
:全索引扫描1. 查询需要获取整颗索引树,才能得到想要的结果时
DESC SELECT countrycode FROM world.city;
2. 联合索引中,任何一个非最左列作为查询条件时
idx_a_b_c(a,b,c) ---> a ab abc
SELECT * FROM t1 WHERE b
SELECT * FROM t1 WHERE c
RANGE
:索引范围扫描,生产中容忍的底线
辅助索引列 > < >= <= LIKE IN OR
DESC SELECT * FROM world.city WHERE Population > 1000000;
DESC SELECT * FROM world.city WHERE Population < 100;
DESC SELECT * FROM world.city WHERE Population >= 1000000;
DESC SELECT * FROM world.city WHERE Population <= 100;
DESC SELECT * FROM world.city WHERE countrycode LIKE 'CH%';
DESC SELECT * FROM world.city WHERE countrycode IN ('CHN','USA');
DESC SELECT * FROM world.city WHERE Population < 100 OR Population > 1000000;
主键列 > < >= <= IN OR <> != NOT IN and between and
DESC SELECT * FROM world.city WHERE id<5;
DESC SELECT * FROM world.city WHERE id>5;
DESC SELECT * FROM world.city WHERE id<=5;
DESC SELECT * FROM world.city WHERE id>=5;
DESC SELECT * FROM world.city WHERE id IN ('1','2' );
DESC SELECT * FROM world.city WHERE id < 5 OR id > 10;
DESC SELECT * FROM world.city WHERE id <> 10;
DESC SELECT * FROM world.city WHERE id != 10;
DESC SELECT * FROM world.city WHERE id NOT IN ('1','2' );
DESC SELECT * FROM world.city WHERE id > 10 and id < 1000;
DESC SELECT * FROM world.city WHERE id between 10 and 1000;
注意:
IN
不能享受到B+树的双向指针DESC SELECT * FROM world.city WHERE countrycode IN ('CHN','USA');
可以改写为
DESC SELECT * FROM world.city WHERE countrycode='CHN' UNION ALL SELECT * FROM world.city WHERE countrycode='USA';
具体效果看压测结果
如果索引列的重复值太多, 改写为
UNION
没有意义
ref
:非唯一性索引的等值查询DESC SELECT * FROM world.city WHERE countrycode='CHN';
eq_ref
:在多表连接查询时,连接条件使用了主键或唯一键索引(uk pK)的非驱动表DESC SELECT b.name,a.name
FROM city a
JOIN country b
ON a.countrycode=b.code
WHERE a.population <100;
const
(system):唯一索引的等值查询DESC SELECT * FROM world.city WHERE id=10;
索引覆盖长度(字节),根据这个值可以判断索引的使用情况,特别是在判断联合索引有多少部分被使用到时。
计算索引列的key_len
key_len和每个索引列的最大预留长度有关
如果没有NOT NULL标记,占用1个字节。如果有NOT NULL标记,不占用字节。
字符类型受字符集影响:latin1编码1个字符1个字节,gbk编码的为1个字符2个字节,utf8编码的1个字符3个字节,utf8mb4编码一个字符4个字节。
变长数据类型,比如:varchar,还有长度信息,需要占用2个字节。
前缀索引指定的索引长度优先,例如:
alter table test add index uri(uri(30));
长度30指的是字符的个数,如果为utf8编码的varchar(255),key_length=30*3+2=92个字节。
数据类型 | 字符集: utf8mb4 | 没有 NOT NULL |
---|---|---|
tinyint | 1 | 1 |
int | 4 | 1 |
char(n) | 4*n | 1 |
varchar(n) | 4*n+2 | 1 |
注:key_len只统计用于where条件过滤时使用的索引列,不包含
order by/group by
部分使用的索引列。
联合索引 idx(a,b,c) 可能有以下应用情况
**优化案例:**a=? and b数字范围 and c=?
idx(k1,num,k2)
desc select * from t100w where k1='Vs' and num<27779 and k2='mnij';
**优化方案:**修改索引为idx(k1,k2,num)。
索引列的基数是重要的参考,对效率的影响更大。
到底是要保证唯一值多的列放在最左侧,还是保证联合索引的覆盖度,最终由压测结果决定。
Using index
:使用索引覆盖
Using where
:使用where回表扫描,部分条件列没有用到索引
Using index condition
:索引条件下推(ICP)
-- 关闭ICP
set global optimizer_switch='index_condition_pushdown=off';
-- 获取执行计划
desc select * from t100w where k1='Vs' and num<27779 and k2='mnij';
# 压测
mysqlslap --defaults-file=/etc/my.cnf \
--concurrency=100 --iterations=1 --create-schema='test' \
--query=" select * from t100w where k1='Vs' and num<27779 and k2='mnij'" engine=innodb \
--number-of-queries=2000 -uroot -p123 -verbose
-- 开启ICP
set global optimizer_switch='index_condition_pushdown=on';
-- 获取执行计划
desc select * from t100w where k1='Vs' and num<27779 and k2='mnij';
# 压测
mysqlslap --defaults-file=/etc/my.cnf \
--concurrency=100 --iterations=1 --create-schema='test' \
--query=" select * from t100w where k1='Vs' and num<27779 and k2='mnij'" engine=innodb \
--number-of-queries=2000 -uroot -p123 -verbose
Using filesort
:使用额外排序(ORDER BY,GROUP BY,DISTINCT)
-- 发现执行计划Extra出现Using filesort
DESC SELECT * FROM world.city WHERE countrycode='CHN' ORDER BY population;
-- 查询列的索引情况
SHOW INDEX FROM world.city;
-- WHERE + (ORDER BY,GROUP BY,DISTINCT) 需要创建联合索引
ALTER TABLE world.city ADD INDEX idx_c_p(CountryCode,Population);
-- 发现执行计划Extra变为NULL
DESC SELECT * FROM world.city WHERE countrycode='CHN' ORDER BY population;
Using temp
:临时表
https://www.cnblogs.com/kerrycode/p/9909093.html
题目意思:我们公司业务慢,请你从数据库的角度分析原因
mysql出现性能问题,我总结有两种情况:
应急性的慢:突然夯住
应急情况:数据库hang(卡了,资源耗尽)
处理过程:
show processlist;
一段时间慢(持续性的):
-- 查看优化器算法
select @@optimizer_switch;
Optimizer Hints:优化器提示 指定单个语句使用的优化器算法
ICP:索引条件下推 默认启用,是针对MySQL使用辅助索引从表中检索行后的一种优化。
SET optimizer_switch = 'index_condition_pushdown=off';
SET optimizer_switch = 'index_condition_pushdown=on';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njvbVFwZ-1608293258785)(MySQL之5—索引及执行计划.assets/QQ图片20201110142917.png)]
如果不使用ICP,辅助索引扫描获取索引元组后,存储引擎将遍历整个基表定位行,并将其返回给MySQL服务器评估WHERE
行的条件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EwxuIVJH-1608293258786)(MySQL之5—索引及执行计划.assets/QQ图片20201110142926.png)]
启用ICP后,辅助索引扫描获取索引元组后,如果WHERE
可以仅使用索引中的列来评估部分条件,则MySQL服务器会将这部分WHERE
条件下降到存储引擎,存储引擎使用索引中的列来评估获取的索引元组,并且只有在满足此条件的情况下,才从基表中读取行。
ICP可以减少存储引擎必须访问基表的次数以及MySQL服务器必须访问存储引擎的次数。
具体示例:
SELECT * FROM people
WHERE zipcode='95054'
AND lastname LIKE '%etrunia%';
如果没有ICP,此查询必须先遍历整个基表,检索符和 zipcode='95054'
条件的表行,然后再将其返回给MySQL服务器评估lastname LIKE '%etrunia%'
条件。
启用ICP,此查询将在遍历整个基表,检索符和 zipcode='95054'
条件的表行前,存储引擎先检查lastname LIKE '%etrunia%'
条件。
适用情况:
range
, ref
, eq_ref
,和 ref_or_null
访问方法时,有一个需要访问的全部表行。InnoDB
和MyISAM
表,包括分区表InnoDB
和 MyISAM
表。InnoDB
表,ICP仅用于二级索引。ICP的目标是减少全行读取次数,从而减少I/O操作。对于 InnoDB
聚集索引,完整的记录已被读入InnoDB
缓冲区。使用ICP不会减少I/O。InnoDB
支持虚拟生成的列上的二级索引。在虚拟生成的列上创建的二级索引不支持ICP。MRR: 多范围读取优化 默认开启,由优化器选择是否使用,关闭则强制使用MRR
SET optimizer_switch = 'mrr_cost_based=off';
SET optimizer_switch = 'mrr_cost_based=on';
不使用MRR:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJl8vtGb-1608293258787)(MySQL之5—索引及执行计划.assets/QQ图片20201110145656.png)]
使用MRR:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TY3iWLRi-1608293258787)(MySQL之5—索引及执行计划.assets/QQ图片20201110145702.png)]
MRR可以减少随机磁盘访问的次数,对基表数据进行更顺序的扫描。
具体示例:
DESC SELECT * FROM world.city WHERE countrycode IN ('CHN','USA');
必须要有主键,业务无关列
为经常作为where,ORDER BY,GROUP BY,join on,distinct的条件(业务: 产品功能+用户行为)的列建立索引
最好使用唯一值多的列作为索引列,如果索引列重复值较多,可以建立联合索引。
字段的值很长的索引列,建议使用前缀索引,前缀索引长度注意评估基数
select count(distinct left(name,19)) from city;
降低索引的数目,不要创建没用的索引,清理不常用或很少使用的索引(pt-duplicate-key-checker)
查看冗余索引
- 查询系统表参数
select table_schema,table_name, redundant_index_name, redundant_index_columns from sys.schema_redundant_indexes;
- SQLyog表信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T9noJKoe-1608293258788)(MySQL之5—索引及执行计划.assets/image-20201110112844669.png)]
索引维护要避开业务繁忙期,建议使用pt-
联合索引最左原则
没有查询条件,或者查询条件没有建立索引
SQL审核和审计
查询结果集是原表中的大部分数据,应该是15-25%以上,优化器觉得就没有必要走索引了(和Mysql的预读功能有关)。
优化:
limit
精确控制查找范围>500 and <1000
索引本身失效,统计数据不真实(过旧)
索引有自我维护的能力,在表内容变化比较频繁的情况下,有可能会出现索引失效。
-- 查看索引统计
select * from mysql.innodb_index_stats;
-- 查看表统计
select * from mysql.innodb_table_stats;
-- 查看当前时间
select now();
-- 1. 删除索引重建
-- 2. 重新收集统计信息
ANALYZE TABLE 表名;
查询条件在索引列上使用函数运算,算术运算包括(+,-,*,/,! 等)或者子查询
-- 错误的SQL
select * from test where id-1=9;
-- 正确的SQL
select * from test where id=10;
隐式转换导致索引失效,是开发中经常会犯的错误,这一点应当引起重视.
mysql> alter table tab add index inx_tel(telnum);
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc tab;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| telnum | varchar(20) | YES | MUL | NULL | |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
-- 正确的SQL
mysql> select * from tab where telnum='1333333';
+------+------+---------+
| id | name | telnum |
+------+------+---------+
| 1 | a | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
-- 错误的SQL
mysql> select * from tab where telnum=1333333;
+------+------+---------+
| id | name | telnum |
+------+------+---------+
| 1 | a | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum='1333333';
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| 1 | SIMPLE | tab | ref | inx_tel | inx_tel | 63 | const | 1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum=1333333;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | tab | ALL | inx_tel | NULL | NULL | NULL | 2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
<>
,not in
,!=
不走索引(辅助索引)
EXPLAIN SELECT * FROM teltab WHERE telnum <> '110';
EXPLAIN SELECT * FROM teltab WHERE telnum NOT IN ('110','119');
EXPLAIN SELECT * FROM teltab WHERE telnum != '110';
>
,<
,in
可能走,也可能不走,和结果集有关,尽量结合业务添加limit
or
,in
尽量改成UNION ALL
like "%_"
百分号在最前面不走索引
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110'
%linux%
类的搜索需求,可以使用专门做搜索服务的数据库产品 elasticsearch + mongodb