性能下降、 SQL 慢、执行时间长、等待时间长的原因分析
create index idx_user_name on user(name)
create index idx_user_nameEmail on user(name,email)
我们手写的 SQL 顺序
MySQL 实际执行 SQL 顺序
常见的 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);
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)
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;
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;
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;
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
select * from tbl_emp e right join tbl_dept d on e.deptId = d.id;
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;
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
where e.id is null
select * from tbl_emp e right join tbl_dept d on e.deptId = d.id where e.id is null;
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
兄弟,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;
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
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;
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)
索引是个什么东东?
将索引理解为"排好序的快速查找数据结构"
索引的优势
索引的劣势
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
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-树更适合实际应用中操作系统的文件索引和数据库索引?】
哪些情况下适合建立索引
哪些情况不要创建索引
案例分析:
MySQL Query Optimizer 的作用
MySQL 常见瓶颈
Explain
是什么?Explain 是查看执行计划
能干嘛?
怎么玩?
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举例
explain
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 | <union1,2> | 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
key_len
ref
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
Extra:包含不适合在其他列中显示但十分重要的额外信息
Using filesort(文件排序):
Using temporary(创建临时表):
Using index(覆盖索引):
表示相应的select操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
覆盖索引(Covering Index),也说为索引覆盖
select *
,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。Using where:表明使用了where过滤
Using join buffer:表明使用了连接缓存
impossible where:where子句的值总是false,不能用来获取任何元组
select tables optimized away:在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)
操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
distinct:优化distinct,在找到第一匹配的元组后即停止找同样值的工作
Explain 热身 Case
,表示查询结果来自一个衍生表,其中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
】