性能分析
Explain
使用EXPLAIN关键字可以模拟优化器(不改变查询结果前提下,调整查询顺序,生成执行计划)执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈
功能:
- 表的读取顺序
- 哪些索引可以使用
- 数据读取操作的操作类型
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被物理查询
格式:Explain + SQL语句
执行出来字段如下:
id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
- id相同,执行顺序由上至下
- id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
- id相同不同,同时存在,id如果相同,可以认为是一组,从上往下顺序执行
在所有组中,id值越大,优先级越高,越先执行,衍生 = DERIVED(虚拟表)
id号每个号码,表示一趟独立的查询。一个sql 的查询趟数越少越好。
select_type
- PRIMARY,主查询(最外围);
- DERIVED,衍生查询(From后的查询,子部分);
- SIMPLE 简单的 select 查询;
- SUBQUERY(在SELECT或WHERE列表中包含了子查询);
- DEPENDENT SUBQUERY(in子查询);
- 若第二个SELECT出现在UNION之后,则被标记为UNION
若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED; - UNION RESULT,从UNION表获取结果的SELECT;
- UNCACHEABLE SUBQUREY(子查询用到了系统变量)
table&partitions
table,显示这一行的数据是关于哪张表的
partitions,代表分区表中的命中情况,非分区表,该项为null
type
显示查询使用了何种类型
从最好到最差依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref
-
system,表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计,@@系统变量就是system,@是在编写存储过程或者触发器程序自定义变量用的
-
const,表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主键置于where列表中,MySQL就能将该查询转换为一个常量
- eq_ref,唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
- ref,非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
-
range,只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
-
index,出现index是sql使用了索引但是没用通过索引进行过滤(Where后没用到),一般是使用了覆盖索引或者是利用索引进行了排序分组。
-
all,Full Table Scan,将遍历全表以找到匹配的行
-
index_merge,在查询过程中需要多个索引组合使用,通常出现在有 or 的关键字的sql中
- ref_or_null,对于某个字段既需要关联条件,也需要null值得情况下。查询优化器会选择用ref_or_null连接查询。
- index_subquery,利用索引来关联子查询,不再全表扫描。
- unique_subquery ,该联接类型类似于index_subquery。 子查询中的唯一索引
possible_keys&key&key_len
possible_keys,可以使用的索引,只能选1个。
key,实际使用的索引。如果为NULL,则没有使用索引。查询中若使用了覆盖索引,则该索引和查询的select字段重叠。
key_len,表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 长度越大越好,定位的数据,key_len字段能够帮你检查是否充分的利用上了索引
如何计算:
1 、先看索引上字段的类型+长度比如 int=4 ; varchar(20) =20 ; char(20) =20
2 、如果是varchar或者char这种字符串字段,视字符集要乘不同的值,比如utf-8,要乘 3,GBK要乘2
3 、varchar这种动态字符串要加2个字节
4、 允许为空的字段要加1个字节
第一组
key_len=age的字节长度+name的字节长度=4+1 + ( 20*3+2)=5+62=67
第二组
key_len=age的字节长度=4+1=5
ref&rows&filtered
ref,显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
rows,rows列显示MySQL认为它执行查询时必须检查的行数。越少越好
filtered,这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。
Extra
包含不适合在其他列中显示但十分重要的额外信息
-
Using filesort ,指手工排序查询,效率极低,排序的字段,排序字段若通过索引去访问将大大提高排序速度(order by字段没索引,很慢)
-
Using temporary ,未使用索引,效率极低,使用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
group by字段没使用索引,超级慢。group by底层包含了order by,所以还有Using filesort
group by字段添加了索引,速度0秒。
-
USING index
利用索引进行了排序或分组
表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找;
如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。 -
Using where,使用排序的索引字段,效率最佳,表明使用了where过滤
-
using join buffer,效率极低,关联字段未使用索引
-
impossible where,where子句的值总是false,不能用来获取任何元组,条件逻辑有误
-
select tables optimized away,使用优化器,效率很好。
在没有GROUP BY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT()操作(执行前就确定COUNT()的值了),不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
查询优化例子
面试题:怎么快速往表中插入一百万数据?
-
一次Insert插入多条数据的方法:insert into report_batch (report_id, batch_id) values (1, 2),(3, 4)…
-
事务关闭,插入一百万数据才提交,只提交一次
-
索引缺点是插入时,需要更改索引。所以插入100w数据可以先删除索引,插入完再创建
-
多线程
-
mybatis批量处理:
//批处理 @Transactional public void add(List<Item> itemList) { SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH,false); ItemMapper mapper = session.getMapper(ItemMapper.class); for (int i = 0; i < itemList.size(); i++) { mapper.insertSelective(itemList.get(i)); if(i%1000==999){//每1000条提交一次防止内存溢出 session.commit(); session.clearCache(); } } session.commit(); session.clearCache(); }
-
sql编程,存储过程
批量数据脚本
建完表,利用sql编程去插入数据,
Redis主从复制,RDB持久化文件,给从节点覆盖执行一次
Mysql主从复制,主机sql写到bin-log,从机读日志文件执行
Mysql主从复制使用函数很容易造成数据不一致问题,比如日期,前后执行时间不一致。所以Mysql一般不允许用户创建函数。但我们又需要sql编程创建函数,就需要修改全局变量设置允许创建函数
show variables like ‘log_bin_trust_function_creators’;
默认关闭,开启如下:
set global log_bin_trust_function_creators=1;
这样添加了参数以后,如果mysqld重启,上述参数又会消失,永久方法:
windows下my.ini[mysqld]加上log_bin_trust_function_creators=1
linux下 /etc/my.cnf下my.cnf[mysqld]加上log_bin_trust_function_creators=1
创建函数
保证每条数据都不同
随机产生字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END $$
#假如要删除
#drop function rand_string;
随机产生部门编号
#用于随机产生多少到多少的编号
DELIMITER $$
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num -from_num+1)) ;
RETURN i;
END$$
#假如要删除
#drop function rand_num;
创建存储过程
procedure pe si
创建往emp表中插入数据的存储过程
DELIMITER $$
CREATE PROCEDURE insert_emp( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
#set autocommit =0 把autocommit设置成0
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO emp (empno, NAME ,age ,deptid ) VALUES ((START+i) ,rand_string(6) , rand_num(30,50),rand_num(1,10000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
#删除
# DELIMITER ;
# drop PROCEDURE insert_emp;
创建往dept表中插入数据的存储过程
#执行存储过程,往dept表添加随机数据
DELIMITER $$
CREATE PROCEDURE `insert_dept`( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept ( deptname,address,ceo ) VALUES (rand_string(8),rand_string(10),rand_num(1,500000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
#删除
# DELIMITER ;
# drop PROCEDURE insert_dept;
调用存储过程
#执行存储过程,往dept表添加1万条数据
DELIMITER ;
CALL insert_dept(10000);
#执行存储过程,往emp表添加50万条数据
DELIMITER ;
CALL insert_emp(100000,500000);
批量删除某个表上的所有索引
mysql索引表位置:
#查询对应库对应表的所有索引名
SELECT index_name FROM information_schema.STATISTICS WHERE table_name='t_emp' AND table_schema='mydb' AND index_name <>'PRIMARY' AND seq_in_index = 1
*<>是标准的, !=是兼容的,一般没啥问题,但有时会故障 所以在数据库中建议用<>*表示不等于
#存储引擎获取所有索引删除
DELIMITER $$
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ct INT DEFAULT 0;
DECLARE _index VARCHAR(200) DEFAULT '';
DECLARE _cur CURSOR FOR SELECT index_name FROM information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND seq_in_index=1 AND index_name <>'PRIMARY' ;
DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
OPEN _cur;
FETCH _cur INTO _index;
WHILE _index<>'' DO
SET @str = CONCAT("drop index ",_index," on ",tablename );
PREPARE sql_str FROM @str ;
EXECUTE sql_str;
DEALLOCATE PREPARE sql_str;
SET _index='';
FETCH _cur INTO _index;
END WHILE;
CLOSE _cur;
END$$
CALL proc_drop_index("dbname","tablename");
CREATE X
DROP INDEX idx_xxx ON emp
1 查出该表有哪些索引,索引名-->集合
SHOW INDEX FROM t_emp
元数据:meta DATA 描述数据的数据
SELECT index_name FROM information_schema.STATISTICS WHERE table_name='t_emp' AND table_schema='mydb'
AND index_name <>'PRIMARY' AND seq_in_index = 1
2 如何循环集合
CURSOR 游标
FETCH xxx INTO xxx
3 如何让mysql执行一个字符串
PREPARE 预编译 XXX
EXECUTE
CALL proc_drop_index ('mydb','t_emp');
索引失效问题
全值匹配我最爱,where后多个字段过滤,使用组合索引,效率更好,匹配更多。
-
最佳左前缀法则,如果是组合索引,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列(第一个必须有,中间不能断),不然索引失效。底层是从左到右一次次匹配B+树,如果中间和第一个断了,无法连接到下一个B+树。每个节点都连接着下个索引字段的B+树。
比如:
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME) #只有age匹配到索引,ken_len只有age的 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name = 'abcd' #ALL,全局匹配,索引失效 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1 AND emp.name = 'abcd'
-
不要在过滤索引列上做任何操作(计算±*/、函数、(自动或者手动)类型转换),会导致索引失效而转向全表扫描
比如:
#索引失效,where过滤字段索引列使用了函数 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(emp.name,3) = 'abc'
-
存储引擎不能使用索引中范围条件右边的列
比如:
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME) #只用到age,deptid,name失效 #deptid范围range查询,组合索引后面发name就失效了 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc'; #改进方法,deptid范围字段放最后 CREATE INDEX idx_age_deptid_name ON emp(age,NAME,deptid)
-
mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
-
is not null 也无法使用索引,但是is null是可以使用索引的
-
like以通配符开头(‘%abc…’)mysql索引失效会变成全表扫描的操作,只要左边有%就索引失效,abc%则不会,B+数安排首字符排序,第一个字符都不清楚,无法定位。
-
字符串不加单引号索引失效
-
or两边的字段中,如果有一个不是索引字段,而其他条件也不是索引字段,会造成该查询不走索引的情况。
索引创建建议:
- 对于单键索引,尽量选择针对当前query过滤性更好的索引(主键、唯一、分支多的,值有很多各种)
- 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
- 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
- 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面
- 书写sql语句时,尽量避免造成索引失效的情况
关联查询优化
#都没索引
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
using join buffer效率极低,添加被驱动表索引:
驱动表字段添加索引是覆盖索引,还是ALL全盘扫描。
#inner会自动去找有索引的表作为被驱动表
EXPLAIN SELECT * FROM class inner JOIN book ON class.card = book.card;
建议把数据小的表放到驱动表中
关联优化对比:
#速度第2
EXPLAIN SELECT SQL_NO_CACHE a.name,
(SELECT c.name FROM emp c WHERE c.id = b.CEO) ceoname
FROM emp a
LEFT JOIN dept b ON a.deptId = b.id;
2个eq_ref,主键唯一索引
#速度第1
EXPLAIN SELECT SQL_NO_CACHE a.name,c.name ceoname FROM emp a
LEFT JOIN dept b ON a.deptId = b.id
LEFT JOIN emp c ON b.CEO = c.id;
id都为1,一趟,2个eq_ref主键唯一索引
#速度第4
EXPLAIN SELECT SQL_NO_CACHE c.name,ab.name ceoname FROM emp c LEFT JOIN
(SELECT a.name,b.id FROM emp a
INNER JOIN dept b On b.ceo = a.id) ab
ON c.deptId = ab.id;
MySQL5.7以下,2个关联,2个ALL,1个eq_ref,1个ref,内连接自动选择数据少的作为驱动表, LEFT JOIN后不要有衍生表,因为衍生表没有索引:
MySQL5.7以上,新版本优化,速度跟第一那个的一样:
#速度第3
EXPLAIN SELECT SQL_NO_CACHE ab.name,c.name ceoname FROM
(SELECT a.name,b.CEO FROM emp a
LEFT JOIN dept b ON a.deptId = b.id) ab
LEFT JOIN emp c ON ab.ceo = c.id;
2个关联,2个ALL,2个eq_ref
总结:
- 保证被驱动表的join字段已经被索引;
- left join 时,选择小表作为驱动表,大表作为被驱动表;
- inner join 时,mysql会自己帮你把小结果集的表选为驱动表;
- 子查询尽量不要放在被驱动表,有可能使用不到索引;
- 能够直接多表关联的尽量直接关联,不用子查询(多一趟ALL)。
子查询优化
尽量不要使用not in 、not null或者 not exists
用left join on xxx is null 替代
SELECT * FROM emp a WHERE a.id NOT IN
(SELECT b.CEO FROM dept b WHERE b.CEO is NOT NULL)
#子查询,多一趟,NOT NULL 索引失效
#优化如下,都用关联查询替换
SELECT * FROM emp a
LEFT JOIN dept on a.id = b.CEO
WHERE b.id IS NULL;
排序分组优化
create index idx_age_deptid_name on emp (age,deptid,name)
以下 是否能使用到索引,能否去掉using filesort
#不能,还是using filesort
1、explain select SQL_NO_CACHE * from emp order by age,deptid;
#能,using filesort去掉了
2、explain select SQL_NO_CACHE * from emp order by age,deptid limit 10;
#1无过滤 不索引,必须有Where,limit等过滤,order by才能使用索引,key_len是where后的索引数
#能,using filesort变成了using index
3、explain select * from emp where age=45 order by deptid;
#能,using filesort变成了using index
4、explain select * from emp where age=45 order by deptid,name;
#能,where、deptid是using index,empno是using filesort
5、explain select * from emp where age=45 order by deptid,empno;
#不能,where是using index,排序是using filesort
6、explain select * from emp where age=45 order by name,deptid;
#都不能
7、explain select * from emp where deptid=45 order by age;
#2顺序错,必filesort排序,order by优化器不调整顺序,调整了结果就变了
#能,using where,过滤和排序都使用了索引
8、explain select * from emp where age=45 order by deptid desc, name desc;
#不能,using where+using filesort
9、explain select * from emp where age=45 order by deptid asc, name desc ;
#3方向反 必filesort排序
ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
例子:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE age = 30 and empno<101000 ORDER BY `name`;
#优化,范围后的索引失效
CREATE INDEX idx_age_name ON emp (age,empno,name)
#去掉范围,去掉using filesort
CREATE INDEX idx_age_name ON emp (age,name)
#2个都创建,mysql选哪个?
CREATE INDEX idx_age_name ON emp (age,empno)#选这个,rows行数才几十,虽然using filesort和range
CREATE INDEX idx_age_name ON emp (age,name)#不选这个,虽然去掉了using filesort,但是rows物理行上万
#分析
原因是所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择。
结论: 当范围条件和group by 或者 order by 的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
filesort两种算法
如果不在索引列上,filesort有两种算法:
mysql就要启动双路排序和单路排序
双路排序:MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
取一批数据,要对磁盘进行了两次扫描,众所周知,I/O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
单路排序:从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。内存排序。
由于单路是后出的,总体而言好过双路,但是用单路有问题:
在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。
本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
优化策略:
- 增大sort_buffer_size参数的设置
- 增大max_length_for_sort_data参数的设置
- 减少select 后面的查询的字段。
提高Order By的速度:
- Order by时select * 是一个大忌,只Query需要的字段, 这点非常重要。在这里的影响是:
1.1 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
1.2 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。 - 尝试提高 sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的 1M-8M之间调整 - 尝试提高 max_length_for_sort_data
提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率. 1024-8192之间调整
group by 使用索引的原则几乎跟order by一致 ,唯一区别是group by 即使没有过滤条件用到索引,也可以直接使用索引
覆盖索引
最后使用索引的手段:覆盖索引
简单说就是,select 到 from 之间查询的列 <=使用的索引列+主键
select * 不要使用,具体用到哪些列哪些,select 具体字段也会使用覆盖索引
explain select * from emp where name like ‘%abc’;
使用覆盖索引后:
优化实战
#1、列出自己的掌门比自己年龄小的人员
SELECT
a.`name`,
a.`age`,
c.`name` ceoname,
c.`age` ceoage
FROM
t_emp a
LEFT JOIN t_dept b ON a.`deptId` = b.`id`
LEFT JOIN t_emp c ON b.`CEO` = c.`id`
WHERE
c.`age` < a.`age`;
#优化
EXPLAIN SELECT SQL_NO_CACHE
a.`name`,
a.`age`,
c.`name` ceoname,
c.`age` ceoage
FROM
emp a
LEFT JOIN dept b ON a.`deptId` = b.`id`
LEFT JOIN emp c ON b.`CEO` = c.`id`
WHERE
c.`age` < a.`age`
CREATE INDEX idx_age ON emp (age)
#2、列出所有年龄低于自己门派平均年龄的人员
SELECT
c.`name`,
c.`age`,
aa.age
FROM
t_emp c
INNER JOIN (
SELECT
a.`deptId`,
AVG(a.`age`) age
FROM
t_emp a
WHERE
a.`deptId` IS NOT NULL
GROUP BY
a.`deptId`
) aa ON c.`deptId` = aa.deptId
WHERE
c.`age` < aa.age;
#优化
EXPLAIN SELECT SQL_NO_CACHE
c.`name`,
c.`age`,
aa.age
FROM
emp c
INNER JOIN (
SELECT
a.`deptId`,
AVG(a.`age`) age
FROM
emp a
WHERE
a.`deptId` IS NOT NULL
GROUP BY
a.`deptId`
) aa ON c.`deptId` = aa.deptid
WHERE
c.`age` < aa.age
CREATE INDEX idx_deptid ON emp (deptid)
CREATE INDEX idx_deptid_age ON emp (deptid, age)
#3、列出至少有2个年龄大于40岁的成员的门派
# select后的字段只能是group后有的或者函数
# 能连接就连接,不要子查询,被驱动表要有索引
# inner join 自动选有索引为被驱动表
# 每一趟只选一个索引
SELECT
b.`deptName`,
COUNT(*)
FROM
t_emp a
INNER JOIN t_dept b ON b.`id` = a.`deptId`
WHERE
a.age > 40
GROUP BY
b.`deptName`,
b.`id`
HAVING
COUNT(*) >= 2;
#优化
EXPLAIN SELECT SQL_NO_CACHE
b.`deptName`,
COUNT(*)
FROM
dept b STRAIGHT_JOIN emp a ON b.`id` = a.`deptId`
WHERE
a.age > 40
GROUP BY
b.`deptName`,
b.`id`
HAVING
COUNT(*) >= 2;
CREATE INDEX idx_deptid_age ON emp (deptid, age)
CREATE INDEX idx_deptname ON dept (deptname)
#STRAIGHT_JOIN 强制确定驱动表和被驱动表 1、概念非常明确 2、对数据量的比例非常明确
#Mysql的inner join 自动选被驱动表,被驱动表虽然有索引,但驱动表可能数据量很大,所以效率反而降低,需要STRAIGHT_JOIN强制确定驱动表,数据量少的在前
#4、至少有2位非掌门人成员的门派
#优化
EXPLAIN SELECT SQL_NO_CACHE
c.deptname,
c.id,
COUNT(*)
FROM
dept c
STRAIGHT_JOIN emp a ON a.`deptId` = c.`id`
LEFT JOIN dept b ON a.`id` = b.`ceo`
WHERE
b.`id` IS NULL
GROUP BY
c.deptname,
c.`id`
HAVING
COUNT(*) >= 2;
CREATE INDEX idx_ceo_deptnam ON dept (ceo, deptname);
CREATE INDEX idx_deptnam ON dept (deptname);
CREATE INDEX idx_deptid ON emp (deptid);
##5、列出全部人员,并增加一列备注“是否为掌门”,如果是掌门人显示是,不是掌门人显示否
SELECT
a.`name`,
a.age,
(
CASE
WHEN b.`id` IS NULL THEN
'否'
ELSE
'是'
END
) '是否为掌门'
FROM
t_emp a
LEFT JOIN t_dept b ON a.`id` = b.`ceo`
#6、列出全部门派,并增加一列备注“老鸟or菜鸟”,若门派的平均值年龄>50显示“老鸟”,否则显示“菜鸟”
SELECT
b.`deptName`,
IF (
AVG(a.age) > 50,
'老鸟',
'菜鸟'
) '老鸟or菜鸟'
FROM
t_emp a
INNER JOIN t_dept b ON a.`deptId` = b.`id`
GROUP BY
b.`id`,
b.`deptName`;
#7、显示每个门派年龄最大的人
SELECT
NAME,
age
FROM
t_emp a
INNER JOIN (
SELECT
deptid,
MAX(age) maxage
FROM
t_emp
WHERE
deptid IS NOT NULL
GROUP BY
deptid
) aa ON a.`age` = aa.maxage
AND a.`deptId` = aa.deptid;
#优化
EXPLAIN SELECT SQL_NO_CACHE
NAME,
age
FROM
emp a
INNER JOIN (
SELECT
deptid,
MAX(age) maxage
FROM
emp
WHERE
deptid IS NOT NULL
GROUP BY
deptid
) aa ON a.`age` = aa.maxage
AND a.`deptId` = aa.deptid;
CREATE INDEX idx_deptid_age ON emp (deptid, age);
#一个@是自定义变量,@@是系统变量
#8、显示每个门派年龄第二大的人,笔试经常出现,排名
SET @rank = 0;
SET @last_deptid = 0;
SELECT
a.deptid,
a. NAME,
a.age
FROM
(
SELECT
t.*,
IF (#组内排序,排名
@last_deptid = deptid ,@rank:=@rank + 1,@rank:= 1
) AS rk,
@last_deptid := deptid AS last_deptid
FROM
t_emp t
ORDER BY
deptid,
age DESC
) a
WHERE
a.rk = 2;
#排名,并列名,相同则排名一样,其他向后推
SET @rank = 0;
SET @last_deptid = 0;
SET @last_age = 0;
SELECT
t.*,
IF (
@last_deptid = deptid,
IF (
@last_age = age ,@rank ,@rank :=@rank + 1
) ,@rank := 1
) AS rk,
@last_deptid := deptid AS last_deptid,
@last_age := age AS last_age
FROM
t_emp t
ORDER BY
deptid,
age DESC
#oracle实习组内排序排名有函数,rank(),over()
#IF(条件,true,false)
CASE
WHEN b.`id` IS NULL THEN
'否'
ELSE
'是'
END