MySQL索引及执行计划
欢迎来到 来到大浪涛天的博客 !
一、索引及执行计划
1. 索引的的简介
- 索引的优势:
MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。打个比方,如果合理的设计且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车。
拿汉语字典的目录页(索引)打比方,我们可以按拼音、笔画、偏旁部首等排序的目录(索引)快速查找到需要的字。索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索引包含多个列。
创建索引时,你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。 - 索引的缺点:
过多的使用索引将会造成滥用。因此索引也会有它的缺点:虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。建立索引会占用磁盘空间的索引文件。 - 简单的说索引就是类似于一本书中的目录,起到优化查询的作用
2. 索引的分类(算法)
B树 默认使用的索引类型
R树
Hash
FullText
GIS 索引
3. BTREE索引

B树索引

3-1. BTREE索引算法演变
B-tree
B+Tree 因为比B树多了双向指针,所以在范围查询方面提供了更好的性能(> < >= <= like)
B*Tree
3-2. Btree索引功能上的分类
3-2-1. 辅助索引

辅助索引
1. 索引是基于表中,列(索引键)的值生成的B树结构
2. 首先提取此列所有的值,进行自动排序
3. 将排好序的值,均匀的分布到索引树的叶子节点中(16K)
4. 然后生成此索引键值所对应得后端数据页的指针
5. 生成枝节点和根节点,根据数据量级和索引键长度,生成合适的索引树高度
id name age gender
select * from t1 where id=10;
问题: 基于索引键做where查询,对于id列是顺序IO,但是对于其他列的查询,可能是随机IO.
3-2-2. 辅助索引(S)怎么构建B树结构的?

辅助索引生成
1. 在建表时,设置了主键列(ID)
2. 在将来录入数据时,就会按照ID列的顺序存储到磁盘上.(我们又称之为聚集索引组织表)
3. 将排好序的整行数据,生成叶子节点.可以理解为,磁盘的数据页就是叶子节点
4. 因为辅助索引上的叶子结点存储了有序的值及聚集索引列值,所以找到了辅助索引上的值后会根据聚集索引列值找到聚集索引的叶子节点也就是硬盘的数据页
3-2-3. 聚集索引
- MySQL 会自动选择主键作为聚集索引列,没有主键会选择唯一键,如果都没有会生成隐藏的.
- MySQL进行存储数据时,会按照聚集索引列值得顺序,有序存储数据行
- 聚集索引直接将原表数据页,作为叶子节点,然后提取聚集索引列向上生成枝和根
3-2-4. 聚集索引和辅助索引的区别
1. 聚集索引只能有一个,非空唯一,一般时主键
2. 辅助索引,可以有多个,时配合聚集索引使用的
3. 聚集索引叶子节点,就是磁盘的数据行存储的数据页
4. MySQL是根据聚集索引,组织存储数据,数据存储时就是按照聚集索引的顺序进行存储数据
5. 辅助索引,只会提取索引键值,进行自动排序生成B树结构

辅助索引和聚集索引
3-2-5. 辅助索引细分
1. 单列辅助索引
2. 联合索引(覆盖索引)
3. 唯一索引
3-2-6. 索引树高度
1. 索引树高度应当越低越好,一般维持在3-4最佳
2. 当数据行数较多
分表 : parttion 用的比较少了.
分片,分布式架构.
3. 字段长度
业务允许,尽量选择字符长度短的列作为索引列
业务不允许,采用前缀索引.
4. 数据类型
char 和 varchar 时最好选择varchar,因为varchar是实际用多少占多少,这样索引占用空间少很多
enum
3-3. 索引的命令操作
3-3-1. 查询索引
desc city;
PRI ==> 主键索引
MUL ==> 辅助索引
UNI ==> 唯一索引
mysql> desc city;
+-------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| Name | char(35) | NO | | | |
| CountryCode | char(3) | NO | MUL | | |
| District | char(20) | NO | | | |
| Population | int(11) | NO | | 0 | |
+-------------+----------+------+-----+---------+----------------+
mysql> show index from city;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| city | 0 | PRIMARY | 1 | ID | A | 4188 | NULL | NULL | | BTREE | | |
| city | 1 | CountryCode | 1 | CountryCode | A | 232 | NULL | NULL | | BTREE | | |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3-3-2. 创建索引
3-3-2-1. 单列的辅助索引:
mysql> alter table city add index idx_name(name);
mysql> show index from city;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| city | 0 | PRIMARY | 1 | ID | A | 4188 | NULL | NULL | | BTREE | | |
| city | 1 | CountryCode | 1 | CountryCode | A | 232 | NULL | NULL | | BTREE | | |
| city | 1 | idx_name | 1 | Name | A | 3998 | NULL | NULL | | BTREE | | |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3-3-2-2. 多列的联合索引:
mysql> alter table city add index idx_c_p(countrycode,population);
mysql> show index from city;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| city | 0 | PRIMARY | 1 | ID | A | 4188 | NULL | NULL | | BTREE | | |
| city | 1 | CountryCode | 1 | CountryCode | A | 232 | NULL | NULL | | BTREE | | |
| city | 1 | idx_name | 1 | Name | A | 3998 | NULL | NULL | | BTREE | | |
| city | 1 | idx_c_p | 1 | CountryCode | A | 232 | NULL | NULL | | BTREE | | |
| city | 1 | idx_c_p | 2 | Population | A | 4052 | NULL | NULL | | BTREE | | |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3-3-2-3. 唯一索引:
mysql> alter table city add unique index uidx_dis(district);
mysql> select count(district) from city;
mysql> select count(distinct district) from city;
3-3-2-4. 前缀索引
当选择的辅助索引的列值太长的话可以选择前缀索引,一个省空间,一个查询得快。
mysql> alter table city add index idx_dis(district(5));
3-3-2-5. 删除索引
mysql> alter table city drop index idx_name;
mysql> alter table city drop index idx_c_p;
mysql> alter table city drop index idx_dis;
3-4. 执行计划分析
3-4-1. 执行计划分析的作用
将优化器 选择后的执行计划 截取出来.便于管理管判断语句得执行效率.
3-4-2. 获取执行计划分析
desc SQL语句
explain SQL 语句
mysql>
mysql> desc select * from test.t100w where k2='MN89';
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | t100w | NULL | ALL | NULL | NULL | NULL | NULL | 1027638 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
3-4-3. 分析执行计划
- table:表名
- type
- 查询的类型:
- 全表扫描 : ALL
- 索引扫描 : index,range,ref,eq_ref,const(system),NULL 按这个顺序性能依次变好
3-4-4. 各个种类查看
- index: 全索引扫描
mysql> desc select countrycode from city;
mysql> desc select countrycode from city;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | index | NULL | CountryCode | 3 | NULL | 4188 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
- range: 索引范围扫描(> < >= <= , between and ,or,in,like )
mysql> desc select * from city where id >1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2094 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
mysql> desc select * from city where countrycode like 'ch%';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | city | NULL | range | CountryCode | CountryCode | 3 | NULL | 397 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
对于辅助索引来讲,!= 和not in等语句是不走索引的,like语句前后都加%匹配也是不走索引,只能后面加%匹配,这样才是连续的数据
对于主键索引列来讲,!= 和not in等语句是走range
mysql> desc select * from city where countrycode like '%ch%';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | ALL | NULL | NULL | NULL | NULL | 4188 | 11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
mysql> desc select * from city where countrycode != 'ch%';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | ALL | CountryCode | NULL | NULL | NULL | 4188 | 97.40 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
mysql> desc select * from city where id != 1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | city | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 3093 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
对于下列语句虽然也是走range索引,但是性能极差,因为搜索的数据不连续,没有使用到b树的优势,b树是双向指针,针对于连续的范围查询的时候如果没查询到不用返回根支点往下搜寻,需要用union all进行改写,如下
mysql> desc select * from city where countrycode='CHN' or countrycode='USA';
mysql> desc select * from city where countrycode in ('CHN','USA');
mysql> desc select * from city where countrycode='chn' or countrycode='usa';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | city | NULL | range | CountryCode | CountryCode | 3 | NULL | 637 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
一般改写为 union all
desc
select * from city where countrycode='CHN'
union all
select * from city where countrycode='USA';
mysql> desc select * from city where countrycode='chn'
-> union all
-> select * from city where countrycode='usa';
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| 1 | PRIMARY | city | NULL | ref | CountryCode | CountryCode | 3 | const | 363 | 100.00 | NULL |
| 2 | UNION | city | NULL | ref | CountryCode | CountryCode | 3 | const | 274 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
- ref: 辅助索引等值查询
mysql> desc select * from city where countrycode='chn';
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | city | NULL | ref | CountryCode | CountryCode | 3 | const | 363 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
- eq_ref : 多表连接时,子表使用主键列或唯一列作为连接条件,子表只的是右边加进来的表,多表查询的时候驱动表是不走索引的,所以子表最好拿主键列来和驱动表匹配,这需要开发事情来规划好哪些列做为主键。
A join B
on a.x = B.y
mysql> desc select b.name,a.name ,a.population
-> from city as a
-> join country as b
-> on a.countrycode=b.code
-> where a.population<100;
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------------+------+----------+-------------+
| 1 | SIMPLE | a | NULL | ALL | CountryCode | NULL | NULL | NULL | 4188 | 33.33 | Using where |
| 1 | SIMPLE | b | NULL | eq_ref | PRIMARY | PRIMARY | 3 | world.a.CountryCode | 1 | 100.00 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+---------------------+------+----------+-------------+
- const(system) : 主键或者唯一键的等值查询,最好是主键,如果是唯一键的话,因为唯一键也是辅助索引,还需要返表查询。
mysql> desc select * from city where id=100;
mysql> desc select * from city where id=100;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | city | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
3-5. 压力测试准备:
mysql> use test
mysql> source /tmp/t100w.sql
压力测试脚本
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 -padmin123 -verbose
3-5-1. 未做优化之前测试
[root@test02 3307]# 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 -padmin123 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
Running for engine rbose
Average number of seconds to run all queries: 829.790 seconds
Minimum number of seconds to run all queries: 829.790 seconds
Maximum number of seconds to run all queries: 829.790 seconds
Number of clients running queries: 100
Average number of queries per client: 20
3-5-2. 针对k2添加索引优化后
mysql> alter table t100w add index idx_k2(k2);
Query OK, 0 rows affected (2.52 sec)
Records: 0 Duplicates: 0 Warnings: 0
[root@test02 3307]# 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 -padmin123 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
Running for engine rbose
Average number of seconds to run all queries: 1.662 seconds
Minimum number of seconds to run all queries: 1.662 seconds
Maximum number of seconds to run all queries: 1.662 seconds
Number of clients running queries: 100
Average number of queries per client: 20