回到正题,关于此次的 MySQL 性能优化的知识点,我会分红两篇幅的文章来输出,关于 SQL 语句的性能优化我会以单独的篇幅来进行编写,语句优化在实操中是属于性能优化的最高级别的优化点,但愿你们也能好好消化。mysql
这个 MySQL 专题是我从年前就一直在准备的,恰好过年在家也没事就一直在思考着要怎么去发表这部分的文章,让你们可以看的时候思路比较清晰,记忆可以更加深入,最后我是经过先发布脑图,而后再根据脑图的方向进行专题知识点的发表,以后应该也会是这种形式,毕竟,这样我写文章思路清晰,你们看文章的时候思路也清晰嘛,复习知识点的时候也能够根据脑图来。程序员
由于开启慢查询日志是有代价的(跟 binlog、optimizer-trace 同样),因此在 MySQL 中,它默认是关闭的:
show variables like 'slow_query%';
复制代码
除了这个开关,还有一个参数,控制执行超过多长时间的 SQL 才记录到慢日志,默认是 10 秒。
show variables like '%slow_query%';
复制代码
能够直接动态修改参数(重启后失效)。
set @@global.slow_query_log=1; -- 1 开启,0 关闭,重启后失效
set @@global.long_query_time=3; -- mysql 默认的慢查询时间是 10 秒,另开一个窗口后才会查到最新值
show variables like '%long_query%';
show variables like '%slow_query%';
复制代码
或者修改配置文件 my.cnf。
如下配置定义了慢查询日志的开关、慢查询的时间、日志文件的存放路径。
slow_query_log = ON
long_query_time=2
slow_query_log_file =/var/lib/mysql/localhost-slow.log
复制代码
模拟慢查询:
select sleep(10);
复制代码
查询 user_innodb 表的 500 万数据(检查是否是没有索引)。
SELECT * FROM `user_innodb` where phone = '136';
复制代码
慢日志分析
日志内容
show global status like 'slow_queries'; -- 查看有多少慢查询 show variables like '%slow_query%'; -- 获取慢日志目录
cat /var/lib/mysql/ localhost-slow.log
复制代码
DROP TABLE IF EXISTS course;
CREATE TABLE `course` (
`cid` int(3) DEFAULT NULL,
`cname` varchar(20) DEFAULT NULL,
`tid` int(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS teacher;
CREATE TABLE `teacher` (
`tid` int(3) DEFAULT NULL,
`tname` varchar(20) DEFAULT NULL,
`tcid` int(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS teacher_contact;
CREATE TABLE `teacher_contact` (
`tcid` int(3) DEFAULT NULL,
`phone` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `course` VALUES ('1', 'mysql', '1');
INSERT INTO `course` VALUES ('2', 'jvm', '1');
INSERT INTO `course` VALUES ('3', 'juc', '2');
INSERT INTO `course` VALUES ('4', 'spring', '3');
INSERT INTO `teacher` VALUES ('1', 'jerry', '1');
INSERT INTO `teacher` VALUES ('2', 'jack', '2');
INSERT INTO `teacher` VALUES ('3', 'mic', '3');
INSERT INTO `teacher_contact` VALUES ('1', '13688888888');
INSERT INTO `teacher_contact` VALUES ('2', '18166669999');
INSERT INTO `teacher_contact` VALUES ('3', '17722225555');
复制代码
explain 的结果有不少的字段,咱们详细地分析一下。
先确认一下环境:
select version();
show variables like '%engine%';
复制代码
id
id 是查询序列编号。
id 值不一样
id 值不一样的时候,先查询 id 值大的(先大后小)。
--查询 mysql 课程的老师手机号
EXPLAIN SELECT tc.phone FROM teacher_contact tc WHERE tcid = (
SELECT tcid FROM teacher t WHERE t.tid = ( SELECT c.tid FROM course c WHERE c.cname = 'mysql')
);
复制代码
简单查询,不包含子查询,不包含关联查询 union。 EXPLAIN SELECT * FROM teacher;
再看一个包含子查询的案例: --查询 mysql 课程的老师手机号
EXPLAIN SELECT tc.phone FROM teacher_contact tc WHERE tcid = (SELECT tcid FROM teacher t WHERE t.tid = ( SELECT c.tid FROM course c WHERE c.cname = 'mysql'));
PRIMARY
子查询 SQL 语句中的主查询,也就是最外面的那层查询。
SUBQUERY
子查询中全部的内层查询都是 SUBQUERY 类型的。
DERIVED
衍生查询,表示在获得最终查询结果以前会用到临时表。例如: --查询 ID 为 1 或 2 的老师教授的课程 EXPLAIN SELECT cr.cname FROM (SELECT * FROM course WHERE tid = 1UNIONSELECT * FROM course WHERE tid = 2 ) cr;
DROP TABLE IF EXISTS single_data;
CREATE TABLE single_data(
id int(3) PRIMARY KEY,
content varchar(20)
);
insert into single_data values(1,'a');
EXPLAIN SELECT * FROM single_data a where id = 1;
复制代码
system
system 是 const 的一种特例,只有一行知足条件。例如:只有一条数据的系统表。 EXPLAIN SELECT * FROM mysql.proxies_priv;
DELETE FROM teacher where tid in (4,5,6);
commit;
复制代码
--备份
INSERT INTO `teacher` VALUES (4, 'james', 4);
INSERT INTO `teacher` VALUES (5, 'tom', 5);
INSERT INTO `teacher` VALUES (6, 'seven', 6);
commit;
复制代码
为 teacher_contact 表的 tcid(第一个字段)建立主键索引。 --ALTER TABLE teacher_contact DROP PRIMARY KEY; ALTER TABLE teacher_contact ADD PRIMARY KEY(tcid); 为teacher 表的 tcid(第三个字段)建立普通索引。 --ALTER TABLE teacher DROP INDEX idx_tcid; ALTER TABLE teacher ADD INDEX idx_tcid (tcid); 执行如下 SQL 语句: select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;
此时的执行计划(teacher_contact 表是 eq_ref):
小结:
以上三种 system,const,eq_ref,都是可遇而不可求的,基本上很难优化到这个状态。
ref
查询用到了非惟一性索引,或者关联操做只使用了索引的最左前缀。 例如:使用 tcid 上的普通索引查询: explain SELECT * FROM teacher where tcid = 3;
range
索引范围扫描。 若是 where 后面是 between and 或 <或 > 或 >= 或 <=或 in 这些,type 类型就为 range。 不走索引必定是全表扫描(ALL),因此先加上普通索引。 --ALTER TABLE teacher DROP INDEX idx_tid; ALTER TABLE teacher ADD INDEX idx_tid (tid); 执行范围查询(字段上有普通索引): EXPLAIN SELECT * FROM teacher t WHERE t.tid <3; --或 EXPLAIN SELECT * FROM teacher t WHERE tid BETWEEN 1 AND 2;
IN 查询也是 range(字段有主键索引)
EXPLAIN SELECT * FROM teacher_contact t WHERE tcid in (1,2,3);
index
Full Index Scan,查询所有索引中的数据(比不走索引要快)。 EXPLAIN SELECT tid FROM teacher;
all
Full Table Scan,若是没有索引或者没有用到索引,type 就是 ALL。表明全表扫描。
NULL
不用访问表或者索引就能获得结果,例如: EXPLAIN select 1 from dual where 1=1;