mysql 优化
导入数据 自己写的一个测试表
CREATE TABLE `usertb` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uname` varchar(20) DEFAULT NULL,
`ucreatetime` datetime DEFAULT NULL,
`age` int(11) DEFAULT NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=594641 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
然后 插入数据用的函数
CREATE DEFINER=`root`@`localhost` PROCEDURE `test1`()
begin
declare v_cnt decimal (10) default 0 ;
dd:loop
insert into usertb values
(null,'用户1','2010-01-01 00:00:00',20),
(null,'用户2','2010-01-01 00:00:00',20),
(null,'用户3','2010-01-01 00:00:00',20),
(null,'用户4','2010-01-01 00:00:00',20),
(null,'用户5','2011-01-01 00:00:00',20),
(null,'用户6','2011-01-01 00:00:00',20),
(null,'用户7','2011-01-01 00:00:00',20),
(null,'用户8','2012-01-01 00:00:00',20),
(null,'用户9','2012-01-01 00:00:00',20),
(null,'用户0','2012-01-01 00:00:00',20)
;
commit;
set v_cnt = v_cnt+10 ;
if v_cnt = 1000000 then leave dd;
end if;
end loop dd ;
end
然后 执行函数
call test1;
因为 现在基本上都是用的innodb 所以 改一下引擎
alter table usertb engine=innodb;
现在可以用 explain来分析sql性能 https://segmentfault.com/a/1190000040477046
查
查询 是sql优化的关键吧 个人认为
最主要的 还是走索引
在所有用于where,order by和group by的列上添加索引
1.什么是索引?
索引是存储引擎中的一种数据结构,或者说数据的组织方式,又称为键key,是存储引擎用于快速找到记录的一种数据结构。
为数据建立索引好比为字典创建音序表,如果要查字,不用音序表的话查询非常困难,建立索引就是这个道理
索引是数据的组织方式,将数据按索引规定的结构组织成一种树型结构,该树叫B+树
2.使用索引的好处
在生产环境中我们遇到最多且容易出问题的是一些比较复杂的查询操作,所以我们要对查询语句进行优化,要加速查询的话,就需要用到索引。
关于索引:
索引并不是越多越好,并且最好提前创建索引
# 索引多了可能会影响到数据库写操作,硬盘IO变高
如果某一张表的ibd文件中创建了很多索引树,意味着进行写操作时(update语句),多个索引树都要发生变化,从而导致硬盘IO变高。
索引的本质就是不断地缩小数据的范围选出最终想要的结果,同时将随机事件变成顺序事件。
知识储备:
# 磁盘的预读
计算机访问一个地址的数据时,与其相邻的数据也会很快被访问到。
每一次IO读取的数据我们称为一页(page),具体一页有多大数据跟操作系统有关,一般为4K到8K,也就是读取一页内的数据时,实际才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。
查询优化:
# SQL层面
添加适当的索引
优化SQL语句的逻辑,尽量使查询所涉及的行变少
# 架构层面
MySQL集群
主从复制
读写分离
MySQL集群前加入Redis缓存
# 补充:
等值查询: where语句 =
范围查询: where语句 < > ...
3.索引分类
#===== B+树索引(InnoDB存储引擎默认)
聚集索引:即主键索引,primary key
用途:
1.加速查找
2.约束(不为空、不能重复)
唯一索引:unique
用途:
1.加速查找
2.约束(不能重复)
普通索引:index
用途:
1.加速查找
联合索引:
primary key(id,name):联合主键索引
unique(id,name):联合唯一索引
index(id,name):联合普通索引
#===== HASH索引(哈希索引,查询单条块,范围查询较慢)
排序和范围查询都很慢
原理:
将数据打散再去查询
InnoDB和Myisam都不支持,设置完还是B树
Memory存储引擎支持
# 值得注意的是,InnoDB存储引擎的内存中的架构中包含一个自适应哈希索引,这个自适应哈希索引是InnoDB为了加速查询性能而自动创建的
#===== FULLTEXT:全文索引 (只可以用在MyISAM引擎中)
通过关键字的匹配来进行查询,,类似于like的模糊匹配
like + %在文本比较少时是合适的,但对于大量的文本数据检索会非常慢
全文索引在大量的数据面前能比like块得多,但是准确度很低
#===== RTREE:R树索引
RTREE在mysql很少使用,仅支持geometry数据类型
geometry数据类型一般填写经纬度那样的数据
支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。
RTREE范围查找很强,但Btree也不弱
# 不同的存储引擎支持的索引类型也不一样
InnoDB支持事务,支持行级别锁定,支持B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory不支持事务,支持表级锁定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB支持事务,支持行级别锁定,支持Hash索引,不支持B-tree、Full-text 等索引;
Archive 不支持事务,支持表级别锁定,不支持 B-tree、Hash、Full-text 等索引;
折叠
4.索引的数据结构
InnoDB存储引擎默认的索引结构为B+树,而B+树是由二叉树、平衡二叉树、B树再到B+树一路演变过来的
二叉查找树
提取每一条记录的id值作为key值,value为本行完整记录
id | user |
---|---|
10 | zs |
7 | ls |
13 | ww |
5 | zl |
8 | xw |
12 | xm |
17 | dy |
以key值的大小为基础构建二叉树,如上图所示
二叉查找树的特点就是任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于当前节点的键值。
顶端的节点我们称为根节点,没有子节点的节点我们称之为叶节点。
如果我们需要查找id=12的用户信息
select * from user where id=12;
查找流程如下:
1.将根节点作为当前节点,把12与当前节点的键值10比较,12大于10,接下来我们把当前节点>的右子节点作为当前节点
2.继续把12和当前节点的键值13比较,发现12小于13,把当前节点的左字节点作为当前节点
3.把12和当前节点的键值12对比,12等于12,满足条件,我们从当前节点中取出data,即id=1>2,name=xm
# 利用二叉树我们需要3次即可找到匹配的数据,如果在表中一条条的查找的话,我们需要6次才能找到。
平衡二叉树
我们回到二叉查找树的特点上,只论二叉查找树,它的特点只是:任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于当前节点的键值。
# 但某种情况下二叉查找树变成了链表,这种现象会导致二叉查找树变得不平衡,也就是高度太高,从而导致查找效率的不稳定。
为了解决这个问题,我们需要保证二叉查找树一直保持平衡,就需要用到平衡二叉树了。平衡二叉树又称AVL树,在满足二叉查找树特性的基础上,要求每个节点的左右子树的高度不能超过1。
下图进行对比:
由平衡二叉树的构造我们可以发现第一张图中的二叉树其实就是一棵平衡二叉树。平衡二叉树保证了树的构造是平衡的,当我们插入或删除数据导致不满足平衡二叉树不平衡时,平衡二叉树会进行调整树上的节点来保持平衡。
具体的调整方式这里就不介绍了。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。
B树
平衡二叉树会因为数据量变大而导致树的高度变高,在查找数据时会进行很多次的磁盘IO,查找数据的效率也会变得极低。
综上,我们要在平衡二叉树的基础上,把更多的节点放入一个磁盘块中,那么平衡二叉树的弊端也就解决了,即构建一个单节点可以存储多个键值对的平衡树,这就是B树
![](https://img-blog.csdnimg.cn/a6f02de966d24749b0839111c0c9316a.jpeg)
从上图可以看出,B树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的节点,子节点的个数一般为阶。
上图中的B树为3阶B树,高度也会很低,基于这个特性,B树查找数据读取磁盘的次数就会很少,数据库的查找效率也会比平衡二叉树高很多。
# B树的不足:对于范围查询,或者说排序操作,B树也不能做得很好。
B+树
B+树是对B树的进一步优化。让我们先来看下B+树的结构图:
1.B+树非叶子节点non-leaf node上是不存储数据的,仅存储键,而B树的非叶子加点中不仅存储键,也会存储数据。B+树之所以这么做的意义在于:树一个节点就是一个页,而数据库中页的大小是固定的,innodb存储引擎默认一页为16KB,所以在页大小固定的前提下,能往一个页中放入更多的节点,相应的树的阶数(节点的子节点树)就会更大,那么树的高度必然更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。
2.B+树的阶数是等于键的数量的,例如上图,我们的B+树中每个节点可以存储3个键,3层B+树存可以存储3*3*3=27个数据。所以如果我们的B+树一个节点可以存储1000个键值,那么3层B+树可以存储1000×1000×1000=10亿个数据。而一般根节点是常驻内存的,所以一般我们查找10亿数据,只需要2次磁盘IO,真是屌炸天的设计。
3、因为B+树索引的所有数据均存储在叶子节点leaf node,而且数据是按照顺序排列的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而B树因为数据分散在各个节点,要实现这一点是很不容易的。
而且B+树中各个页之间也是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。其实上面的B树我们也可以对各个节点加上链表。其实这些不是它们之前的区别,是因为在mysql的innodb存储引擎中,索引就是这样存储的。也就是说上图中的B+树索引就是innodb中B+树索引真正的实现方式,准确的说应该是聚集索引(聚集索引和非聚集索引下面会讲到)。
通过上图可以看到,在innodb中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。
MyISAM中的B+树索引实现与innodb中的略有不同。在MyISAM中,B+树索引的叶子节点并不存储数据,而是存储数据的文件地址。
方便分组 范围查询 排序 去重查找
聚簇索引 索引和数据在一个文件夹 innoDB
非聚簇索引 索引和数据分开存放 MyISAM
5.索引管理
创建/删除索引的语法
# 方法一:创建表时
create table 表名 (
字段名1 数据类型 [完整性约束条件...],
字段名2 数据类型 [完整性约束条件...],
[unique | fulltext | spatial] index | key
[索引名] (字段名[(长度)] [asc | desc])
);
案例:
mysql> create table t11 (id int primary key);
Query OK, 0 rows affected (0.01 sec)
mysql> show create table t11\G
*************************** 1. row ***************************
Table: t11
Create Table: CREATE TABLE `t11` (
`id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
# 方法二:create在已存在的表上创建索引
create [unique | fulltext | spatial ] index 索引名 on 表名 (字段名[(长度)] [asc | desc]);
案例:
mysql> create unique index index12 on t12 (id);
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table t12\G
*************************** 1. row ***************************
Table: t12
Create Table: CREATE TABLE `t12` (
`id` int(11) DEFAULT NULL,
UNIQUE KEY `index12` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
# 方法三:alter table 在已存在的表上创建索引
alter table 表名 add [unique | fulltext | spatial] index 索引名 (字段名[(长度)] [asc | desc]);
案例:
mysql> create table t13(id int);
Query OK, 0 rows affected (0.00 sec)
mysql> alter table t13 add unique index index13 (id);
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table t13\G
*************************** 1. row ***************************
Table: t13
Create Table: CREATE TABLE `t13` (
`id` int(11) DEFAULT NULL,
UNIQUE KEY `index13` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
# 删除索引
drop index 索引名 on 表名字;
alter table 表名字 drop index 索引名字;
# 删除主键索引
alter table 表名字 drop primary key;
案例:
mysql> drop index index13 on t13;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table t13\G
*************************** 1. row ***************************
Table: t13
Create Table: CREATE TABLE `t13` (
`id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
# 查看索引
方法一:
mysql> desc t12;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | YES | UNI | NULL | |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)
方法二:
mysql> show index from t12\G
*************************** 1. row ***************************
Table: t12
Non_unique: 0
Key_name: index12
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
1 row in set (0.00 sec)
折叠
索引示例
# 主键索引(聚集索引)
# 创建主键索引
mysql> alter table student add primary key pri_id(id);
mysql> create table student(id int not null, primary key(id));
mysql> create table student(id int not null primary key auto_increment comment '学号');
# 注意:
database 可以写为 schema
index 可以写为 key
# 唯一键索引
# 创建唯一键索引
mysql> alter table country add unique key uni_name(name);
mysql> create table student(id int unique key comment '学号');
mysql> create unique key index index_name on table_name(id);
# 普通索引(辅助索引)
# 普通索引的创建
mysql> alter table student add index idx_gender(gender);
mysql> create index index_name on table_name (column_list);
# 创建前缀索引
按照该列数据的前n个字母创建索引
mysql> alter table student add index idx_name(name(4));
# 全文索引
# 针对content做了全文索引:
CREATE TABLE t1 (
id int NOT NULL AUTO_INCREMENT,
title char(255) NOT NULL,
content text,
PRIMARY KEY (id),
FULLTEXT (content));
查找时:
select * from table where match(content) against('想2查询的字符串');
折叠
6.正确的使用索引
并不是创建了索引就一定会加快查询速度,若想利用索引达到预想的提高查询速度的效果,我们添加索引时,必须遵循以下问题。
一.索引不存储null值
更准确的说,单列索引不存储null值,复合索引不存储全为null的值。索引不能存储Null,所以对这列采用is null条件时,因为索引上根本
没Null值,不能利用到索引,只能全表扫描。
为什么索引列不能存Null值?
将索引列值进行建树,其中必然涉及到诸多的比较操作。Null值的特殊性就在于参与的运算大多取值为null。
这样的话,null值实际上是不能参与进建索引的过程。也就是说,null值不会像其他取值一样出现在索引树的叶子节点上。
二.不适合键值较少的列(重复数据较多的列)
假如索引列TYPE有5个键值,如果有1万条数据,那么 WHERE TYPE = 1将访问表中的2000个数据块。
再加上访问索引块,一共要访问大于200个的数据块。
如果全表扫描,假设10条数据一个数据块,那么只需访问1000个数据块,既然全表扫描访问的数据块少一些,肯定就不会利用索引了。
三.前导模糊查询不能利用索引(like '%XX’或者like ‘%XX%’)
假如有这样一列code的值为’AAA’,‘AAB’,‘BAA’,‘BAB’ ,如果where code like '%AB’条件,由于前面是
模糊的,所以不能利用索引的顺序,必须一个个去找,看是否满足条件。这样会导致全索引扫描或者全表扫
描。如果是这样的条件where code like 'A % ',就可以查找CODE中A开头的CODE的位置,当碰到B开头的
数据时,就可以停止查找了,因为后面的数据一定不满足要求。这样就可以利用索引了。
四.索引失效的几种情况
1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
no 有唯一索引
2.对于多列索引,不是使用的第一部分,则不会使用索引
3.like查询以%开头
select * ``from` `tb1 ``where` `email ``like` `'%cn'``;
4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
select * ``from` `tb1 ``where` `email = 999;
5.如果mysql估计使用全表扫描要比使用索引快,则不使用索引
6.普通索引的不等于不会走索引
- !=`` ``select` `* ``from` `tb1 ``where` `email != ``'alex'`` ` ` ``--特别的:如果是主键,则还是会走索引`` ``select` `* ``from` `tb1 ``where` `nid != 123``- >`` ``select` `* ``from` `tb1 ``where` `email > ``'alex'`` ` ` ` ` ``--特别的:如果是主键或索引是整数类型,则还是会走索引`` ``select` `* ``from` `tb1 ``where` `nid > 123`` ``select` `* ``from` `tb1 ``where` `num > 123
7.组合索引最左前缀
如果组合索引为:(name,email)
name and email – 使用索引
name – 使用索引
email – 不使用索引
8.不在索引列做运算或者使用函数
9.尽量避免使用 in和not in
如果可以 用between and 代替
10.在索引列不要用子查询 如果要用 用exists代替 in 详细用法
口诀
模 型 数 空 运 最 快
口诀字面意思就是,要运送一个产品模型的话,要用空运,不要用陆运和海运,数空运最快。叫作:模型数空运最快。
下面我拆开逐字讲解一下:
模:模糊查询的意思。like的模糊查询以%开头,索引失效。比如:
SELECT * FROM user
WHERE name
LIKE ‘%老猿’; %失效的原因是因为索引是按第一个字的hash值进行排序的
型:代表数据类型。类型错误,如字段类型为varchar,where条件用number,索引也会失效。比如:
SELECT * FROM user
WHERE height= 180;
height为varchar类型导致索引失效。
数:是函数的意思。对索引的字段使用内部函数,索引也会失效。这种情况下应该建立基于函数的索引。比如:
SELECT * FROM user
WHERE DATE(create_time) = ‘2020-09-03’;
create_time字段设置索引,那就无法使用函数,否则索引失效。
空:是Null的意思。索引不存储空值,如果不限制索引列是not null,数据库会认为索引列有可能存在空值,所以不会按照索引进行计算。比如:
SELECT * FROM user
WHERE address IS NULL不走索引。
SELECT * FROM user
WHERE address IS NOT NULL;走索引。
建议大家这设计字段的时候,如果没有必要的要求必须为NULL,那么最好给个默认值空字符串,这可以解决很多后续的麻烦(切记)。
运:是运算的意思。对索引列进行(+,-,*,/,!, !=, <>)等运算,会导致索引失效。比如:
SELECT * FROM user
WHERE age - 1 = 20;
最:是最左原则。在复合索引中索引列的顺序至关重要。如果不是按照索引的最左列开始查找,则无法使用索引。带头大哥不能死 中间兄弟不能断
快:全表扫描更快的意思。如果数据库预计使用全表扫描要比使用索引快,则不使用索引。
范围之后全失效 使用范围查询之后的索引全部失效 B+树 范围的那个索引无效 导致之后的索引无效
7.索引其他知识
索引默认排序 asc
回表和二级索引
除了主键索引是聚簇索引 mysql的innodb 其他索引都是非聚簇索引 都是二级索引 第一次会查到id和索引列的数据 如果需要其他的数据 则会进行回表
ICP
索引下推 是mysql 5.6针对扫描二级索引的一项优化 减少回表次数
以前是查到一条数据回表一次 现在是总共回表一次
减少要查询的列
一般来说 需要什么就查什么 不要直接select *
查询尽可能使用 limit 做分页
减少返回的行数,减少数据传输时间和带宽浪费
使用缓存
对于一些不经常变更的数据 可以用缓存存储 再查询
其他
1、外连接的效率要比子查询的效率高 子查询会查两次 一次外部查询 一次嵌套子查询 子查询通常会使用临时表或者内存表
2、union all 要比 union 效率高
3、如果数据量很大的话 可以用ElasticSearch
3、尽量使用where 代替 having
4 不用null != 和 or
5 有时可以使用 force index 强制使用索引
6 解决 %查询导致索引失效时 我们可以使用覆盖索引
having只会在检索出所有记录之后才会对结果集进行过滤 这个处理需要排序分组 也可以用可以用ElasticSearch
7 查询范围尽可能小 范围
8 小表驱动大表 减少被驱动表的扫描次数
增
开启线程
使用批处理
开启事务 在事务中进行操作
删
1、如果 要删除的数据占了很大一部分的时候 把需要的数据导出到新表 然后老表使用drop删除掉
导出到新表 使用 insert 一次不宜过多 50w左右 最好使用id作为条件
改
replace into
replace into 是insert的增强版
replace into 首先尝试插入数据到表中,
- 如果发现表中已经有此行数据(根据主键或者唯一索引判断)则先删除此行数据,然后插入新的数据。
- 否则,直接插入新数据
这将导致 如果插入的数据不全 将会有空值产生
注意
1、插入数据的表必须有主键或者是唯一索引!否则的话,replace into 会直接插入数据,这将导致表中出现重复的数据。
2、如果数据库里边有这条记录,则直接修改这条记录;如果没有则,则直接插入,在有外键的情况下,对主表进行这样操作时,因为如果主表存在一条记录,被从表所用时,直接使用replace into是会报错的,这和replace into的内部原理是相关(先删除然后再插入)。
3、replace操作在自增主键的情况下,遇到唯一键冲突时执行的是delete+insert,但是在记录binlog时,却记录成了update操作,update操作不会涉及到auto_increment的修改。备库应用了binlog之后,备库的表的auto_increment属性不变。如果主备库发生主从切换,备库变为原来的主库,写新的主库则有风险发生主键冲突
频繁的REPLACE INTO 会造成新纪录的主键的值迅速增大。总有一天。达到最大值后就会因为数据太大溢出了。就没法再插入新纪录了。数据表满了,不是因为空间不够了,而是因为主键的值没法再增加了。
批量update
一条记录update一次,性能很差
UPDATE categories
SET display_order = CASE id
WHEN 1 THEN 3
WHEN 2 THEN 4
WHEN 3 THEN 5
END,
title = CASE id
WHEN 1 THEN 'New Title 1'
WHEN 2 THEN 'New Title 2'
WHEN 3 THEN 'New Title 3'
END
WHERE id IN (1,2,3)
insert into …on duplicate key update
insert into test_tbl (id,dr) values (1,'2'),(2,'3'),...(x,'y') on duplicate key update dr=values(dr);
创建临时表,先更新临时表,然后从临时表中update
CREATE TEMPORARY TABLE tmp (
id int(4) primary key,
dr varchar(50)
);
INSERT INTO tmp VALUES (0,'gone'), (1,'xx'),...(m,'yy');
UPDATE test_tbl, tmp SET test_tbl.dr=tmp.dr WHERE test_tbl.id=tmp.id;
死锁
查看死锁的原因 到 选择一个事务进行终结
- 并发插入时,不在一个事务内进行再次事务提交
- 通过其他手段,如预创建账户,解决这个要并发插入的问题
- 改并发为串行执行
trace跟踪和explain
使用trace
1, 打开trace,设置格式为JSON,设置trace的缓存大小,避免因为容量大小而不能显示完整的跟踪过程。
set optimizer_trace=“enabled=on”,end_markers_in_JSON=on;
2,执行想做trace的SQL语句
select * from t_pay_order_info where biz_code = ‘B202305220001’ and biz_order_code = ‘B202305220002’;
3,查询information_schema.optimizer_trace,可以看到跟踪记录了
select * from information_schema.optimizer_trace
trace结构
{
"steps": [
{
"join_preparation": { -- 第一阶段:SQL准备阶段,格式化sql
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `t_student`.`id` AS `id`,`t_student`.`std_name` AS `std_name`,`t_student`.`age` AS `age`,`t_student`.`class_id` AS `class_id`,`t_student`.`gmt_create` AS `gmt_create` from `t_student` where (`t_student`.`std_name` > 'a') order by `t_student`.`age`"
}
] /* steps */
} /* join_preparation */
},
{
"join_optimization": { -- 第二阶段:SQL优化阶段
"select#": 1,
"steps": [
{
"condition_processing": { -- 条件处理
"condition": "WHERE",
"original_condition": "(`t_student`.`std_name` > 'a')",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "(`t_student`.`std_name` > 'a')"
},
{
"transformation": "constant_propagation",
"resulting_condition": "(`t_student`.`std_name` > 'a')"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "(`t_student`.`std_name` > 'a')"
}
] /* steps */
} /* condition_processing */
},
{
"substitute_generated_columns": {
} /* substitute_generated_columns */
},
{
"table_dependencies": [ -- 表依赖详情
{
"table": "`t_student`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
] /* depends_on_map_bits */
}
] /* table_dependencies */
},
{
"ref_optimizer_key_uses": [
] /* ref_optimizer_key_uses */
},
{
"rows_estimation": [ -- 预估表的访问成本
{
"table": "`t_student`",
"range_analysis": {
"table_scan": { -- 全表扫描
"rows": 100300, -- 行数
"cost": 20351 -- 查询消耗
} /* table_scan */,
"potential_range_indexes": [ -- 查询可能使用的索引
{
"index": "PRIMARY", -- 主键索引
"usable": false, -- 未使用
"cause": "not_applicable" -- 原因:不适合
},
{
"index": "idx_std_age", -- age索引
"usable": false, -- 未使用
"cause": "not_applicable" -- 原因:不适合
},
{
"index": "idx_std_name_age_class", -- stdname,age,class的组合索引
"usable": true, -- 使用
"key_parts": [
"std_name",
"age",
"class_id",
"id"
] /* key_parts */
}
] /* potential_range_indexes */,
"setup_range_conditions": [
] /* setup_range_conditions */,
"group_index_range": { -- group 用到的索引
"chosen": false, -- 未使用
"cause": "not_group_by_or_distinct" -- 原因:未使用group by 或者 distinct
} /* group_index_range */,
"analyzing_range_alternatives": { -- 分析各个索引使用成本
"range_scan_alternatives": [
{
"index": "idx_std_name_age_class",
"ranges": [
"a < std_name" -- 索引使用范围
] /* ranges */,
"index_dives_for_eq_ranges": true,
"rowid_ordered": false, -- 使用该索引获取的记录是否按照主键排序
"using_mrr": false,
"index_only": false, -- 是否使用覆盖索引
"rows": 50150, -- 索引扫描行数
"cost": 60181, -- 索引使用成本
"chosen": false, -- 是否选择该索引:否
"cause": "cost" -- 原因:消耗
}
] /* range_scan_alternatives */,
"analyzing_roworder_intersect": { -- 分析使用索引合并的成本
"usable": false,
"cause": "too_few_roworder_scans"
} /* analyzing_roworder_intersect */
} /* analyzing_range_alternatives */
} /* range_analysis */
}
] /* rows_estimation */
},
{
"considered_execution_plans": [ -- 分析出的执行计划
{
"plan_prefix": [
] /* plan_prefix */,
"table": "`t_student`",
"best_access_path": { -- 最优访问路径
"considered_access_paths": [ --分析出的最终访问路径
{
"rows_to_scan": 100300,
"access_type": "scan", -- 访问类型:为scan,全表扫描
"resulting_rows": 100300,
"cost": 20349,
"chosen": true, -- 确定选择
"use_tmp_table": true
}
] /* considered_access_paths */
} /* best_access_path */,
"condition_filtering_pct": 100,
"rows_for_plan": 100300,
"cost_for_plan": 20349,
"sort_cost": 100300,
"new_cost_for_plan": 120649,
"chosen": true
}
] /* considered_execution_plans */
},
{
"attaching_conditions_to_tables": { -- 为查询的表添加条件
"original_condition": "(`t_student`.`std_name` > 'a')",
"attached_conditions_computation": [
] /* attached_conditions_computation */,
"attached_conditions_summary": [ -- 添加条件结果
{
"table": "`t_student`",
"attached": "(`t_student`.`std_name` > 'a')"
}
] /* attached_conditions_summary */
} /* attaching_conditions_to_tables */
},
{
"clause_processing": { -- order by 处理
"clause": "ORDER BY",
"original_clause": "`t_student`.`age`",
"items": [
{
"item": "`t_student`.`age`"
}
] /* items */,
"resulting_clause_is_simple": true,
"resulting_clause": "`t_student`.`age`"
} /* clause_processing */
},
{
"reconsidering_access_paths_for_index_ordering": { -- 重构索引处理顺序
"clause": "ORDER BY",
"steps": [
] /* steps */,
"index_order_summary": {
"table": "`t_student`",
"index_provides_order": false,
"order_direction": "undefined",
"index": "unknown",
"plan_changed": false
} /* index_order_summary */
} /* reconsidering_access_paths_for_index_ordering */
},
{
"refine_plan": [
{
"table": "`t_student`"
}
] /* refine_plan */
}
] /* steps */
} /* join_optimization */
},
{
"join_execution": { -- 第三阶段:SQL执行阶段
"select#": 1,
"steps": [
{
"filesort_information": [
{
"direction": "asc",
"table": "`t_student`",
"field": "age"
}
] /* filesort_information */,
"filesort_priority_queue_optimization": {
"usable": false,
"cause": "not applicable (no LIMIT)"
} /* filesort_priority_queue_optimization */,
"filesort_execution": [
] /* filesort_execution */,
"filesort_summary": {
"rows": 100000,
"examined_rows": 100000,
"number_of_tmp_files": 14,
"sort_buffer_size": 262016,
"sort_mode": "<sort_key, packed_additional_fields>"
} /* filesort_summary */
}
] /* steps */
} /* join_execution */
}
] /* steps */
}
explain
列名 | 描述 |
---|---|
id | 查询中每个 select 操作的顺序标识符 |
select_type | select 操作的类型(例如,简单查询、主查询、子查询等) |
table | select 操作涉及的表的名称 |
partitions | select 操作访问或引用的分区 |
type | 使用的连接类型(例如,ALL、index、range、ref、eq_ref、const、system、NULL) |
possible_keys | 可用于 select 操作的可能索引 |
key | 实际用于 select 操作的索引 |
key_len | 使用的索引的长度 |
ref | 与 select 操作的索引一起使用的列或常量 |
rows | 估计要检查的行数 |
filtered | 可能被 select 操作过滤掉的行的百分比 |
Extra | 关于 select 操作的其他信息(例如,Using where、Using index 等) |
dependencies | select 操作依赖的表或列 |