本文共 21806 字,大约阅读时间需要 72 分钟。
性能下降、 SQL 慢、执行时间长、等待时间长的原因分析
create index idx_user_name on user(name)
create index idx_user_nameEmail on user(name,email)
我们手写的 SQL 顺序
MySQL 实际执行 SQL 顺序
总结:mysql 从 FROM 开始执行~
常见的 JOIN 查询图
建表 SQL
CREATE TABLE tbl_dept( id INT(11) NOT NULL AUTO_INCREMENT, deptName VARCHAR(30) DEFAULT NULL, locAdd VARCHAR(40) DEFAULT NULL, PRIMARY KEY(id))ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE tbl_emp ( id INT(11) NOT NULL AUTO_INCREMENT, NAME VARCHAR(20) DEFAULT NULL, deptId INT(11) DEFAULT NULL, PRIMARY KEY (id), KEY fk_dept_Id (deptId) #CONSTRAINT 'fk_dept_Id' foreign key ('deptId') references 'tbl_dept'('Id'))ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT INTO tbl_dept(deptName,locAdd) VALUES('RD',11);INSERT INTO tbl_dept(deptName,locAdd) VALUES('HR',12);INSERT INTO tbl_dept(deptName,locAdd) VALUES('MK',13);INSERT INTO tbl_dept(deptName,locAdd) VALUES('MIS',14);INSERT INTO tbl_dept(deptName,locAdd) VALUES('FD',15);INSERT INTO tbl_emp(NAME,deptId) VALUES('z3',1);INSERT INTO tbl_emp(NAME,deptId) VALUES('z4',1);INSERT INTO tbl_emp(NAME,deptId) VALUES('z5',1);INSERT INTO tbl_emp(NAME,deptId) VALUES('w5',2);INSERT INTO tbl_emp(NAME,deptId) VALUES('w6',2);INSERT INTO tbl_emp(NAME,deptId) VALUES('s7',3);INSERT INTO tbl_emp(NAME,deptId) VALUES('s8',4);INSERT INTO tbl_emp(NAME,deptId) VALUES('s9',51);123456789101112131415161718192021222324252627282930
mysql> select * from tbl_dept;+----+----------+--------+| id | deptName | locAdd |+----+----------+--------+| 1 | RD | 11 || 2 | HR | 12 || 3 | MK | 13 || 4 | MIS | 14 || 5 | FD | 15 |+----+----------+--------+5 rows in set (0.00 sec)1234567891011
mysql> select * from tbl_emp;+----+------+--------+| id | NAME | deptId |+----+------+--------+| 1 | z3 | 1 || 2 | z4 | 1 || 3 | z5 | 1 || 4 | w5 | 2 || 5 | w6 | 2 || 6 | s7 | 3 || 7 | s8 | 4 || 8 | s9 | 51 |+----+------+--------+8 rows in set (0.00 sec)
7 种 JOIN 示例
笛卡尔积
select * from tbl_emp, tbl_dept;
mysql> select * from tbl_emp, tbl_dept;+----+------+--------+----+----------+--------+| id | NAME | deptId | id | deptName | locAdd |+----+------+--------+----+----------+--------+| 1 | z3 | 1 | 1 | RD | 11 || 1 | z3 | 1 | 2 | HR | 12 || 1 | z3 | 1 | 3 | MK | 13 || 1 | z3 | 1 | 4 | MIS | 14 || 1 | z3 | 1 | 5 | FD | 15 || 2 | z4 | 1 | 1 | RD | 11 || 2 | z4 | 1 | 2 | HR | 12 || 2 | z4 | 1 | 3 | MK | 13 || 2 | z4 | 1 | 4 | MIS | 14 || 2 | z4 | 1 | 5 | FD | 15 || 3 | z5 | 1 | 1 | RD | 11 || 3 | z5 | 1 | 2 | HR | 12 || 3 | z5 | 1 | 3 | MK | 13 || 3 | z5 | 1 | 4 | MIS | 14 || 3 | z5 | 1 | 5 | FD | 15 || 4 | w5 | 2 | 1 | RD | 11 || 4 | w5 | 2 | 2 | HR | 12 || 4 | w5 | 2 | 3 | MK | 13 || 4 | w5 | 2 | 4 | MIS | 14 || 4 | w5 | 2 | 5 | FD | 15 || 5 | w6 | 2 | 1 | RD | 11 || 5 | w6 | 2 | 2 | HR | 12 || 5 | w6 | 2 | 3 | MK | 13 || 5 | w6 | 2 | 4 | MIS | 14 || 5 | w6 | 2 | 5 | FD | 15 || 6 | s7 | 3 | 1 | RD | 11 || 6 | s7 | 3 | 2 | HR | 12 || 6 | s7 | 3 | 3 | MK | 13 || 6 | s7 | 3 | 4 | MIS | 14 || 6 | s7 | 3 | 5 | FD | 15 || 7 | s8 | 4 | 1 | RD | 11 || 7 | s8 | 4 | 2 | HR | 12 || 7 | s8 | 4 | 3 | MK | 13 || 7 | s8 | 4 | 4 | MIS | 14 || 7 | s8 | 4 | 5 | FD | 15 || 8 | s9 | 51 | 1 | RD | 11 || 8 | s9 | 51 | 2 | HR | 12 || 8 | s9 | 51 | 3 | MK | 13 || 8 | s9 | 51 | 4 | MIS | 14 || 8 | s9 | 51 | 5 | FD | 15 |+----+------+--------+----+----------+--------+40 rows in set (0.00 sec)
inner join
select * from tbl_emp e inner join tbl_dept d on e.deptId = d.id;
mysql> select * from tbl_emp e inner join tbl_dept d on e.deptId = d.id;+----+------+--------+----+----------+--------+| id | NAME | deptId | id | deptName | locAdd |+----+------+--------+----+----------+--------+| 1 | z3 | 1 | 1 | RD | 11 || 2 | z4 | 1 | 1 | RD | 11 || 3 | z5 | 1 | 1 | RD | 11 || 4 | w5 | 2 | 2 | HR | 12 || 5 | w6 | 2 | 2 | HR | 12 || 6 | s7 | 3 | 3 | MK | 13 || 7 | s8 | 4 | 4 | MIS | 14 |+----+------+--------+----+----------+--------+7 rows in set (0.00 sec)
left join
select * from tbl_emp e left join tbl_dept d on e.deptId = d.id;
mysql> select * from tbl_emp e left join tbl_dept d on e.deptId = d.id;+----+------+--------+------+----------+--------+| id | NAME | deptId | id | deptName | locAdd |+----+------+--------+------+----------+--------+| 1 | z3 | 1 | 1 | RD | 11 || 2 | z4 | 1 | 1 | RD | 11 || 3 | z5 | 1 | 1 | RD | 11 || 4 | w5 | 2 | 2 | HR | 12 || 5 | w6 | 2 | 2 | HR | 12 || 6 | s7 | 3 | 3 | MK | 13 || 7 | s8 | 4 | 4 | MIS | 14 || 8 | s9 | 51 | NULL | NULL | NULL |+----+------+--------+------+----------+--------+8 rows in set (0.00 sec)
right join
mysql> select * from tbl_emp e right join tbl_dept d on e.deptId = d.id;+------+------+--------+----+----------+--------+| id | NAME | deptId | id | deptName | locAdd |+------+------+--------+----+----------+--------+| 1 | z3 | 1 | 1 | RD | 11 || 2 | z4 | 1 | 1 | RD | 11 || 3 | z5 | 1 | 1 | RD | 11 || 4 | w5 | 2 | 2 | HR | 12 || 5 | w6 | 2 | 2 | HR | 12 || 6 | s7 | 3 | 3 | MK | 13 || 7 | s8 | 4 | 4 | MIS | 14 || NULL | NULL | NULL | 5 | FD | 15 |+------+------+--------+----+----------+--------+8 rows in set (0.00 sec)
left join without common part
where d.id is null
select * from tbl_emp e left join tbl_dept d on e.deptId = d.id where d.id is null;
mysql> select * from tbl_emp e left join tbl_dept d on e.deptId = d.id where d.id is null;+----+------+--------+------+----------+--------+| id | NAME | deptId | id | deptName | locAdd |+----+------+--------+------+----------+--------+| 8 | s9 | 51 | NULL | NULL | NULL |+----+------+--------+------+----------+--------+1 row in set (0.00 sec)
right join without common part
select * from tbl_emp e right join tbl_dept d on e.deptId = d.id where e.deptId is null;
mysql> select * from tbl_emp e right join tbl_dept d on e.deptId = d.id where e.id is null;+------+------+--------+----+----------+--------+| id | NAME | deptId | id | deptName | locAdd |+------+------+--------+----+----------+--------+| NULL | NULL | NULL | 5 | FD | 15 |+------+------+--------+----+----------+--------+1 row in set (0.00 sec)
full join
union联合查询自带去重功能
兄弟,mysql 不支持 full join
,但是我们可以通过骚操作实现 full join ,union 关键字用于连接结果集,并且自动去重
tbl_emp 与 tbl_dept 的公共部分 + tbl_emp 表的独有部分 + tbl_dept表的独有部分:将 left join 的结果集和 right join 的结果集使用 union 合并即可
select * from tbl_emp e left join tbl_dept d on e.deptId = d.id
union
select * from tbl_emp e right join tbl_dept d on e.deptId = d.id;
mysql> select * from tbl_emp e left join tbl_dept d on e.deptId = d.id -> union -> select * from tbl_emp e right join tbl_dept d on e.deptId = d.id;+------+------+--------+------+----------+--------+| id | NAME | deptId | id | deptName | locAdd |+------+------+--------+------+----------+--------+| 1 | z3 | 1 | 1 | RD | 11 || 2 | z4 | 1 | 1 | RD | 11 || 3 | z5 | 1 | 1 | RD | 11 || 4 | w5 | 2 | 2 | HR | 12 || 5 | w6 | 2 | 2 | HR | 12 || 6 | s7 | 3 | 3 | MK | 13 || 7 | s8 | 4 | 4 | MIS | 14 || 8 | s9 | 51 | NULL | NULL | NULL || NULL | NULL | NULL | 5 | FD | 15 |+------+------+--------+------+----------+--------+9 rows in set (0.00 sec)
full join without common part
tbl_emp 表的独有部分 + tbl_dept表的独有部分
select * from tbl_emp e left join tbl_dept d on e.deptId = d.id where d.id is null
union
select * from tbl_emp e right join tbl_dept d on e.deptId = d.id where e.deptId is null;
mysql> select * from tbl_emp e left join tbl_dept d on e.deptId = d.id where d.id is null -> union -> select * from tbl_emp e right join tbl_dept d on e.deptId = d.id where e.id is null;+------+------+--------+------+----------+--------+| id | NAME | deptId | id | deptName | locAdd |+------+------+--------+------+----------+--------+| 8 | s9 | 51 | NULL | NULL | NULL || NULL | NULL | NULL | 5 | FD | 15 |+------+------+--------+------+----------+--------+2 rows in set (0.00 sec)
.
索引是个什么东东?
将索引理解为"排好序的快速查找数据结构"
加快col2的查找
,可以维护一个右边所示的二叉查找树(索引的数据结构),每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应索引键来获取左侧对应的值,从而快速的检索出符合条件的记录。索引的优势
排好序的快速查找的数据结构:查找
+ 排序
提高数据检索效率
,降低数据库的IO成本降低数据排序成本
,降低了CPU的消耗索引的劣势
占用空间
的花时间研究
建立优秀的索引,或优化查询语句mysql 索引分类
参考资料:https://www.cnblogs.com/luyucheng/p/6289714.html
建立索引的 SQL 语句
创建索引:
CREATE [UNIQUE] INDEX indexName ON mytable(columnname(length));' or 'ALTER mytable ADD [UNIQUE] INDEX [indexName] ON(columnname(length));
删除索引
DROP INDEX [indexName] ON mytable;
查看索引(\G
表示将查询到的横向表格纵向输出,方便阅读)
SHOW INDEX FROM table_name\G
使用 ALTER 命令,有四种方式来添加数据表的索引:
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,用于全文索引。带你看看 mysql 索引:Index_type 为 BTREE
mysql> show index from tbl_emp;+---------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |+---------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| tbl_emp | 0 | PRIMARY | 1 | id | A | 8 | NULL | NULL | | BTREE | | || tbl_emp | 1 | fk_dept_Id | 1 | deptId | A | 8 | NULL | NULL | YES | BTREE | | |+---------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+2 rows in set (0.00 sec)
.
Btree 索引搜索过程
【初始化介绍】
【查找过程】
B+tree 索引搜索过程
【B+Tree 与 BTree 的区别】
B-树的:关键字(数据项)和记录【实际数据】是放在一起的;
B+树的:非叶子节点中只有关键字和指向下一个节点的索引, 记录【实际数据】只放在叶子节点中。
【B+Tree 与 BTree 的查找过程】
【性能提升】
真实的情况是, 3 层的 B+ 树可以表示上百万的数据, 如果上百万的数据查找只需要三次 IO, 性能提高将是巨大的,如果没有索引, 每个数据项都要发生一次 IO, 那么总共需要百万次的 IO, 显然成本非常非常高。
【思考: 为什么说 B+树比 B-树更适合实际应用中操作系统的文件索引和数据库索引?】
写代价更低
:B+树的内部结点并没有指向关键字具体信息的指针
。 因此其内部结点相对 B 树更小
。 如果把所有同一内部结点的关键字存放在同一盘块中, 那么盘块所能容纳的关键字数量也越多。 一次性读入内存中的需要查找的关键字也就越多。 相对来说 IO 读写次数低
。查询效率更加稳定
:由于非终结点并不是最终指向文件内容的结点, 而只是叶子结点中关键字的索引。 所以任何关键字的查找必须走一条从根结点到叶子结点的路。 所有关键字查询的路径长度相同
, 导致每一个数据的查询效率相当
。哪些情况下适合建立索引
哪些情况不要创建索引
案例分析:
.
MySQL Query Optimizer 的作用
MySQL 常见瓶颈
Explain
是什么?Explain 是查看执行计划
能干嘛?
怎么玩?
mysql> explain select * from tbl_emp;+----+-------------+---------+------+---------------+------+---------+------+------+-------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------+------+---------------+------+---------+------+------+-------+| 1 | SIMPLE | tbl_emp | ALL | NULL | NULL | NULL | NULL | 8 | NULL |+----+-------------+---------+------+---------------+------+---------+------+------+-------+1 row in set (0.00 sec)
id:select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
相同顺序走,不同看谁大,大的它先行
id 取值的三种情况:
id相同,执行顺序由上至下
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
id相同不同,同时存在:id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行;衍生=DERIVED
select_type:查询的类型,主要用于区别普通查询、联合查询、子查询等复杂查询
UNION 和 UNION RESULT举例
xplain -> select * from tbl_emp e left join tbl_dept d on e.deptId = d.id -> union -> select * from tbl_emp e right join tbl_dept d on e.deptId = d.id;+----+--------------+------------+------+---------------+------------+---------+-----------+------+----------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+--------------+------------+------+---------------+------------+---------+-----------+------+----------------------------------------------------+| 1 | PRIMARY | e | ALL | NULL | NULL | NULL | NULL | 8 | NULL || 1 | PRIMARY | d | ALL | PRIMARY | NULL | NULL | NULL | 5 | Using where; Using join buffer (Block Nested Loop) || 2 | UNION | d | ALL | NULL | NULL | NULL | NULL | 5 | NULL || 2 | UNION | e | ref | fk_dept_Id | fk_dept_Id | 5 | db01.d.id | 1 | NULL || NULL | UNION RESULT || ALL | NULL | NULL | NULL | NULL | NULL | Using temporary |+----+--------------+------------+------+---------------+------------+---------+-----------+------+----------------------------------------------------+5 rows in set (0.00 sec)
table:显示这一行的数据是关于哪张表的
type:访问类型排列,显示查询使用了何种类型
访问类型
,是较为重要的一个指标,结果值从最好到最坏依次是:system>const>eq_ref>ref>fultext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL
system>const>eq_ref>ref>range>index>ALL
,一般来说,得保证查询至少达到range级别,最好能达到ref。从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL
system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计
const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据
,所以很快。如将主键置于where列表中,MySQL就能将该查询转换为一个常量
eq_ref:唯一性索引,对于每个索引键,表中只有一条记录
与之匹配,常见于主键
或唯一索引扫描
ref:非唯一索引扫描,返回匹配某个单独值的所有行
。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
range:只检索给定范围的行
,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的where语句中出现了between
、<
、>
、in
等的查询这种范围扫描索引扫描比全表扫描要好,因为他只需要开始索引的某一点,而结束于另一点,不用扫描全部索引
index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘数据库文件中读的)
all:FullTable Scan,将遍历全表以找到匹配的行(全表扫描)
备注:一般来说,得保证查询只是达到range级别,最好达到ref
possible_keys
理论上用到的索引
】key
实际使用的索引,如果为null,则没有使用索引【实际上用到的索引
】
若查询中使用了覆盖索引,则该索引仅出现在key列表中
key_len
条件越多越精确
不损失精确性的情况下,长度越短越好
ref
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数【越少越好
】
Extra:包含不适合在其他列中显示但十分重要的额外信息
Using filesort
、Using temporary
、Using index
;【重点关注】
Using filesort(文件排序):
Using temporary(创建临时表):
Using index(覆盖索引):
表示相应的select操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
覆盖索引(Covering Index),也说为索引覆盖
只用从索引中就能够取得
,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。select *
,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。Using where:表明使用了where过滤
Using join buffer:表明使用了连接缓存
impossible where:where子句的值总是false,不能用来获取任何元组【where过滤条件有误
】
select tables optimized away:在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)
操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
distinct:优化distinct,在找到第一匹配的元组后即停止找同样值的工作
Explain 热身 Case
<derived3>
,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1.name ...
】select id, name from t1 where other_column= ' '
】select id from t3
】select name, id from t2
】感谢尚硅谷的视频
https://www.bilibili.com/video/BV1KW411u7vy . 和 . oneby-wang大佬的笔记参考 https://blog.csdn.net/oneby1314/article/details/107938325