MySQL详解(四)——高级 2.0
性能分析
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批量处理:
//批处理@Transactionalpublic 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))
BEGINDECLARE 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 emp1 查出该表有哪些索引,索引名-->集合
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 = 12 如何循环集合CURSOR 游标FETCH xxx INTO xxx3 如何让mysql执行一个字符串
PREPARE 预编译 XXXEXECUTECALL 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、列出自己的掌门比自己年龄小的人员
SELECTa.`name`,a.`age`,c.`name` ceoname,c.`age` ceoage
FROMt_emp a
LEFT JOIN t_dept b ON a.`deptId` = b.`id`
LEFT JOIN t_emp c ON b.`CEO` = c.`id`
WHEREc.`age` < a.`age`;
#优化
EXPLAIN SELECT SQL_NO_CACHEa.`name`,a.`age`,c.`name` ceoname,c.`age` ceoage
FROMemp a
LEFT JOIN dept b ON a.`deptId` = b.`id`
LEFT JOIN emp c ON b.`CEO` = c.`id`
WHEREc.`age` < a.`age`CREATE INDEX idx_age ON emp (age) #2、列出所有年龄低于自己门派平均年龄的人员
SELECTc.`name`,c.`age`,aa.age
FROMt_emp c
INNER JOIN (SELECTa.`deptId`,AVG(a.`age`) ageFROMt_emp aWHEREa.`deptId` IS NOT NULLGROUP BYa.`deptId`
) aa ON c.`deptId` = aa.deptId
WHEREc.`age` < aa.age;#优化
EXPLAIN SELECT SQL_NO_CACHEc.`name`,c.`age`,aa.age
FROMemp c
INNER JOIN (SELECTa.`deptId`,AVG(a.`age`) ageFROMemp aWHEREa.`deptId` IS NOT NULLGROUP BYa.`deptId`
) aa ON c.`deptId` = aa.deptid
WHEREc.`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 自动选有索引为被驱动表
# 每一趟只选一个索引
SELECTb.`deptName`,COUNT(*)
FROMt_emp a
INNER JOIN t_dept b ON b.`id` = a.`deptId`
WHEREa.age > 40
GROUP BYb.`deptName`,b.`id`
HAVINGCOUNT(*) >= 2;#优化
EXPLAIN SELECT SQL_NO_CACHEb.`deptName`,COUNT(*)
FROMdept b STRAIGHT_JOIN emp a ON b.`id` = a.`deptId`
WHEREa.age > 40
GROUP BYb.`deptName`,b.`id`
HAVINGCOUNT(*) >= 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_CACHEc.deptname,c.id,COUNT(*)
FROMdept c
STRAIGHT_JOIN emp a ON a.`deptId` = c.`id`
LEFT JOIN dept b ON a.`id` = b.`ceo`
WHEREb.`id` IS NULL
GROUP BYc.deptname,c.`id`
HAVINGCOUNT(*) >= 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、列出全部人员,并增加一列备注“是否为掌门”,如果是掌门人显示是,不是掌门人显示否
SELECTa.`name`,a.age,(CASEWHEN b.`id` IS NULL THEN'否'ELSE'是'END) '是否为掌门'
FROMt_emp a
LEFT JOIN t_dept b ON a.`id` = b.`ceo`#6、列出全部门派,并增加一列备注“老鸟or菜鸟”,若门派的平均值年龄>50显示“老鸟”,否则显示“菜鸟”
SELECTb.`deptName`,
IF (AVG(a.age) > 50,'老鸟','菜鸟'
) '老鸟or菜鸟'
FROMt_emp a
INNER JOIN t_dept b ON a.`deptId` = b.`id`
GROUP BYb.`id`,b.`deptName`;#7、显示每个门派年龄最大的人
SELECTNAME,age
FROMt_emp a
INNER JOIN (SELECTdeptid,MAX(age) maxageFROMt_empWHEREdeptid IS NOT NULLGROUP BYdeptid
) aa ON a.`age` = aa.maxage
AND a.`deptId` = aa.deptid;#优化
EXPLAIN SELECT SQL_NO_CACHENAME,age
FROMemp a
INNER JOIN (SELECTdeptid,MAX(age) maxageFROMempWHEREdeptid IS NOT NULLGROUP BYdeptid
) 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;SELECTa.deptid,a. NAME,a.age
FROM(SELECTt.*,IF (#组内排序,排名@last_deptid = deptid ,@rank:=@rank + 1,@rank:= 1) AS rk,@last_deptid := deptid AS last_deptidFROMt_emp tORDER BYdeptid,age DESC) a
WHEREa.rk = 2;#排名,并列名,相同则排名一样,其他向后推
SET @rank = 0;
SET @last_deptid = 0;
SET @last_age = 0;SELECTt.*,
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
FROMt_emp t
ORDER BYdeptid,age DESC
#oracle实习组内排序排名有函数,rank(),over()#IF(条件,true,false)
CASE
WHEN b.`id` IS NULL THEN'否'
ELSE'是'
END
MySQL详解(四)——高级 2.0相关推荐
- mysql数据库设计四大范性_数据库篇-mysql详解( 四 )之范式与数据高级骚操作
标题.png 一 : 主键 primary key,主要的键. 一张表只能有一个字段可以使用对应的键, 用来唯一的约束该字段里面的数据, 不能重复: 这种称之为主键.一张表只能有最多一个主键 操作 增 ...
- MySql详解(四)
MySql详解(四) MySql的DML操作 插入: 一.方式一 语法: insert into 表名(字段名,...) values(值,...); 特点: 1.要求值的类型和字段的类型要一致或兼容 ...
- 在虚拟机安装MySQL详解
在虚拟机安装MySQL详解 MySQL是一种关系型数据库,原本是瑞典的公司叫MySqlAB公司,后被Oracle收购 DB:database,数据库,里边保存了有组织的规范的数据 DBMS:datab ...
- MySql详解(六)
MySql详解(六) MySql事务 一.含义 事务:一条或多条sql语句组成一个执行单位,一组sql语句要么都执行要么都不执行 二.特点(ACID) A 原子性:一个事务是不可再分割的整体,要么都执 ...
- 详解ZStack高级功能--裸金属服务
详解Zstack高级功能--裸金属服务 一.前言 今天我们来了解一下ZStack的裸金属,提到裸金属服务,很多人从字面上可能对其不是很了解,其实早在之前的私有云OpenStack平台,就已经推行了Ir ...
- Mysql详解——索引优化
本篇文章是对Mysql索引的创建以及优化进行一个介绍,关于索引的底层原理可以看我另一篇文章:Mysql详解--索引详解 文章目录 一.索引的创建和设计原则 1. 索引的声明和使用 1.1 索引的分类: ...
- linux 进程间通信 dbus-glib【实例】详解四(上) C库 dbus-glib 使用(附代码)(编写接口描述文件.xml,dbus-binding-tool工具生成绑定文件)(列集散集函数)
linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...
- Android Studio 插件开发详解四:填坑
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78265540 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...
- springboot 详解 (四)redis filter
---------------------------------------------------------------------------------------------------- ...
- 大数据WEB阶段(六)MySql详解(二)
MySql详解(二) 一.分组查询 语法: select col_name1,col_name2... from tb_name group by having ...; 练习: 执行下面的SQL,创 ...
最新文章
- 计算机考试批处理试题,2015计算机三级考试pc技术模拟试题及答案(八)
- 怎么在html中加判断,css样式里面如何做判断
- C++ Primer 5th笔记(chap 19 特殊工具与技术)控制内存分配
- 大学计算机测试试题,大学计算机基础 excel测试题 求答案~~喵~~
- 使用iOS手势UIGestureRecognizer
- Python 3.6学习笔记(一)
- LeetCode 23. 合并K个排序链表(优先队列)
- Spark集群试运行
- select标签multiple属性的使用方法
- 【Tensor】(张量)的创建
- 【Silverlight】Bing Maps学习系列(一):开发前的准备工作
- 科研绘图必备软件简介
- mysql关联分组查询,Mysql 分组查询/子查询/关联查询【总结】
- # java 核心技术卷1 (原书第11版)通读 第一章:java的基本程序设计结构
- SOP封装的后缀字母L M N都代表什么意思?
- extern关键字的作用
- B树与B+树简明扼要的分析
- 效率源linux,FLOOPY效率源硬盘坏道修复工具 修复坏硬盘的时候 为何只能手动修复 而自动修复又说找不到硬盘?...
- java 参数不知道类型_java – 类型参数不在其范围内
- 渗透测试之SQL注入