ALL:全表扫描(最坏情况,必须避免) MySQL会扫描整张表的所有数据,找到符合条件的记录; 比如没有索引的表,执行 SELECT * FROM user WHERE age = 20,就会走ALL; 特点:效率极低,数据量越大,执行越慢。只要type是ALL,优先检查是否能加索引、是否走对了索引;
index:全索引扫描(和ALL差不多,尽量避免) 扫描整个索引表,好处是不需要对数据进行排序(因为索引本身是有序的),但本质还是全量扫描,效率依然很低; 比如索引idx_name,执行 SELECT name FROM user,MySQL会扫描整个idx_name索引(不用回表查数据),这时type就是index;
ref:非唯一索引扫描(推荐,争取达到) 使用了非唯一索引,或者唯一索引的非唯一性前缀,返回的数据可能是多条(索引列的值不唯一); 不需要扫描全索引(因为索引有序),即使有重复值,也只在小范围内扫描,效率比range高; 比如非唯一索引idx_age,执行 SELECT * FROM user WHERE age = 20,这时type就是ref(因为age可能有多个值);
eq_ref:唯一性索引扫描(优秀,尽量争取) 使用了主键,或唯一索引时产生的扫描方式,通常出现在多表联查中; 每次查询只会返回一条匹配的数据(由于主键/唯一索引的值唯一),效率很高; 比如主键id,执行 SELECT * FROM user u JOIN order o ON u.id = o.user_id,这时u.id是主键,type就是eq_ref;
const:常量级扫描(最优,理想状态) 效率最高的扫描方式,适用于结果只有一条的主键/唯一索引查询,且查询条件是常量值比较; 和eq_ref对比,const是和常量值比较,比如 SELECT * FROM user WHERE id = 1,而eq_ref通常用于多表联查;
Extra:额外执行信息(辅助判断效率)
Extra信息方便进行性能分析,它反映了MySQL在执行过程中的细节,说明下最常见的几种取值
Using filesort:严重低效,必须避免 MySQL无法利用索引完成排序,只能在内存(Sort Buffer)甚至磁盘中进行手动排序; 常见场景如:ORDER BY 或 GROUP BY 的字段没有索引,或者联合索引顺序不匹配; 优化方法:让GROUP BY、ORDER BY的字段和索引字段一致,确保排序字段满足最左匹配原则,利用B+树天然的有序性;
Using temporary:严重低效,必须避免 MySQL发现由于数据过于复杂,不得不建立一张临时表来中转数据(通常是为了去重或分组);临时表会占用内存/磁盘资源,数据量越大,效率越低; 常见场景如:在非索引列上进行DISTINCT、GROUP BY,或者关联查询时 ORDER BY驱动表以外的列; 优化方法:给GROUP BY、ORDER BY的字段加索引,避免创建临时表,或者优化 SQL 逻辑减少中间结果集;
Using index:高效,推荐 查询所需的所有数据,都能在索引中找到,不需要回表查询原表数据,也就是常说的覆盖索引;避免了回表操作(回表是低效操作),执行效率很高; 比如索引idx_price_name(price, name),执行SELECT name FROM user WHERE price = 20时不需要回表,索引中能直接获取name;
Using index condition:进阶优化(补漏必备) 索引下推(ICP)是MySQL 5.6+的黑科技,就是在索引内部提前过滤掉不符合条件的记录,从而减少回表次数; 虽然不如Using index快,但它代表你的联合索引正在被高效利用,是一个性能良好的信号;
Using where:最常见的状态 MySQL服务器(Server层)在存储引擎检索行后,还需要进行一次筛选;就是存储引擎给的数据并不完全符合条件,Server层得再做一次筛选;
Using index for group-by:分组优化巅峰 类似于Using index,MySQL发现只需利用索引树,就能完成GROUP BY或 DISTINCT,连排序都不用了,极度高效; 意味着MySQL连索引树的所有叶子节点都不用扫,直接通过索引的索引(松散索引扫描)就能拿到结果。
SELECT*FROMuser u1 INNERJOIN ( SELECT id FROMuserWHERE user_name >= {lastName} and id > {lastId} ORDERBY user_name, id LIMIT 10 ) u2 ON u1.id = u2.id ORDERBY u1.user_name, u1.id
关联更新、删除
UPDATE operation o SET status ='applying' WHERE o.id IN (SELECT id FROM (SELECT o.id, o.status FROM operation o WHERE o.group =123AND o.status NOTIN ( 'done' ) ORDERBY o.parent, o.id LIMIT 1) t);
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+ | 1 | PRIMARY | o | index | | PRIMARY | 8 | | 24 | Using where; Using temporary | | 2 | DEPENDENT SUBQUERY | | | | | | | | Impossible WHERE noticed after reading const tables | | 3 | DERIVED | o | ref | idx_2,idx_5 | idx_5 | 8 | const | 1 | Using where; Using filesort | +----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
可以使用JOIN重写,子查询模式从DEPENDENT SUBQUERY变为DERIVED
UPDATE operation o JOIN (SELECT o.id, o.status FROM operation o WHERE o.group =123AND o.status NOTIN ( 'done' ) ORDERBY o.parent, o.id LIMIT 1) t ON o.id = t.id SET status ='applying'
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+ | 1 | PRIMARY | | | | | | | | Impossible WHERE noticed after reading const tables | | 2 | DERIVED | o | ref | idx_2,idx_5 | idx_5 | 8 | const | 1 | Using where; Using filesort | +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
混合排序
SELECT*FROM my_order o INNERJOIN my_appraise a ON a.orderid = o.id ORDERBY a.is_reply ASC, a.appraise_time DESC LIMIT 0, 20
+----+-------------+-------+--------+---------------+---------+---------+------------+---------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+------------+---------+----------------+ | 1 | SIMPLE | a | ALL | idx_orderid | NULL | NULL | NULL | 1967647 | Using filesort | | 1 | SIMPLE | o | eq_ref | PRIMARY | PRIMARY | 122 | a.orderid | 1 | NULL | +----+-------------+-------+--------+---------------+---------+---------+------------+---------+----------------+
如果is_reply只有0和1两种状态,可以使用下面的方法重写
SELECT*FROM ( (SELECT*FROM my_order o INNERJOIN my_appraise a ON a.orderid = o.id AND is_reply =0 ORDERBY appraise_time DESC LIMIT 0, 20) UNIONALL (SELECT*FROM my_order o INNERJOIN my_appraise a ON a.orderid = o.id AND is_reply =1 ORDERBY appraise_time DESC LIMIT 0, 20)) t ORDERBY is_reply ASC, appraisetime DESC LIMIT 20;
EXISTS语句
SELECT*FROM my_neighbor n LEFTJOIN my_neighbor_apply sra ON n.id = sra.neighbor_id AND sra.user_id ='xxx' WHERE n.topic_status <4 ANDEXISTS(SELECT1FROM message_info m WHERE n.id = m.neighbor_id AND m.inuser ='xxx') AND n.topic_type <>5
+----+--------------------+-------+------+---------------+------------------+---------+-------+---------+------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+------+---------------+------------------+---------+-------+---------+------------------------------------+ | 1 | PRIMARY | n | ALL | | NULL | NULL | NULL | 1086041 | Using where | | 1 | PRIMARY | sra | ref | | idx_user_id | 123 | const | 1 | Using where | | 2 | DEPENDENT SUBQUERY | m | ref | | idx_message_info | 122 | const | 1 | Using index condition; Using where | +----+--------------------+-------+------+---------------+------------------+---------+-------+---------+------------------------------------+
将exists改为join,能够避免嵌套子查询
SELECT*FROM my_neighbor n INNERJOIN message_info m ON n.id = m.neighbor_id AND m.inuser ='xxx' LEFTJOIN my_neighbor_apply sra ON n.id = sra.neighbor_id AND sra.user_id ='xxx' WHERE n.topic_status <4AND n.topic_type <>5
+----+-------------+-------+--------+---------------+------------------+---------+-----------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+------------------+---------+-----------+------+-----------------------+ | 1 | SIMPLE | m | ref | | idx_message_info | 122 | const | 1 | Using index condition | | 1 | SIMPLE | n | eq_ref | | PRIMARY | 122 | ighbor_id | 1 | Using where | | 1 | SIMPLE | sra | ref | | idx_user_id | 123 | const | 1 | Using where | +----+-------------+-------+--------+---------------+------------------+---------+-----------+------+-----------------------+
提前过滤
SELECT*FROM my_order o LEFTJOIN my_userinfo u ON o.uid = u.uid LEFTJOIN my_productinfo p ON o.pid = p.pid WHERE ( o.display =0 ) AND ( o.ostaus =1 ) ORDERBY o.selltime DESC LIMIT 0, 15
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+ | 1 | SIMPLE | o | ALL | NULL | NULL | NULL | NULL | 909119 | Using where; Using temporary; Using filesort | | 1 | SIMPLE | u | eq_ref | PRIMARY | PRIMARY | 4 | o.uid | 1 | NULL | | 1 | SIMPLE | p | ALL | PRIMARY | NULL | NULL | NULL | 6 | Using where; Using join buffer (Block Nested Loop) | +----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+
先做一系列的左连接,然后排序取前15条记录,从执行计划也可以看出,参与排序的记录数为90万
SELECT*FROM ( SELECT*FROM my_order o WHERE o.display =0AND o.ostaus =1 ORDERBY o.selltime DESC LIMIT 0, 15 ) o LEFTJOIN my_userinfo u ON o.uid = u.uid LEFTJOIN my_productinfo p ON o.pid = p.pid ORDERBY o.selltime DESC limit 0, 15
+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 15 | Using temporary; Using filesort | | 1 | PRIMARY | u | eq_ref | PRIMARY | PRIMARY | 4 | o.uid | 1 | NULL | | 1 | PRIMARY | p | ALL | PRIMARY | NULL | NULL | NULL | 6 | Using where; Using join buffer (Block Nested Loop) | | 2 | DERIVED | o | index | NULL | idx_1 | 5 | NULL | 909112 | Using where | +----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+