SQL优化之博客案例
问题背景: 问题SQL: SELECT DISTINCT post.`ID` AS postId,post.`post_title`,post.`post_content`,post.`post_excerpt`,u.`display_name`,(SELECT IFNULL(GROUP_CONCAT(term.name),'') FROM wp_term_relationships AS r LEFT JOIN wp_term_taxonomy AS t ON(r.term_taxonomy_id = t.term_taxonomy_id) LEFT JOIN wp_terms AS term ON(term.term_id = t.term_id) WHERE r.object_id = post.ID AND t.taxonomy = 'category') AS categoryName,1)">post_tag) AS tagName,post.`comment_count`,post.`post_status`,post.`post_date` FROM wp_posts AS post LEFT JOIN wp_users AS u ON(post.`post_author` = u.`ID`) LEFT JOIN wp_term_relationships AS relation ON(relation.`object_id` = post.`ID`) LEFT JOIN wp_term_taxonomy AS taxonomy ON(taxonomy.`term_taxonomy_id` =relation.`term_taxonomy_id`) LEFT JOIN wp_terms AS term ON(term.`term_id` = taxonomy.`term_id`) WHERE post.`post_type` = post AND post.`post_status` IN (publish) ORDER BY post.`post_date` DESC LIMIT 0,10 将这段sql放在sqlyog里执行,结果花费时间如下: 优化后的SQL: SELECT DISTINCT post.`ID` AS postId,1)"> t.term_id) WHERE r.object_id = post.ID AND t.taxonomy = ' AND `post_status` IN (') ORDER BY `post_date` DESC LIMIT 10 ) AS post LEFT JOIN wp_users AS u ON(post.`post_author` = u.`ID`) LEFT JOIN wp_term_relationships AS relation ON(relation.`object_id` = post.`ID`) LEFT JOIN wp_term_taxonomy AS taxonomy ON(taxonomy.`term_taxonomy_id` =relation.`term_taxonomy_id`) LEFT JOIN wp_terms AS term ON(term.`term_id` = taxonomy.`term_id`) 将这段sql放在sqlyog里执行,结果花费时间如下: 优化过后,直接是毫秒级。结果项目在测试环境下访问不卡了。 主要的改动把查询和过滤条件从最后面嵌入到主表的子查询里。 那么问题SQL为什么会这么慢?而优化过后的SQL为什么会突然一下如此迅速到毫秒级呢?先看问题一,为什么问题SQL会这么慢,问题SQL: SELECT DISTINCT post.`ID` AS postId,1)"> t.term_id) WHERE r.object_id = post.ID AND t.taxonomy = u.`ID`) LEFT JOIN wp_term_relationships AS relation ON(relation.`object_id` = post.`ID`) LEFT JOIN wp_term_taxonomy AS taxonomy ON(taxonomy.`term_taxonomy_id` =relation.`term_taxonomy_id`) LEFT JOIN wp_terms AS term ON(term.`term_id` = taxonomy.`term_id`) WHERE post.`post_type` = AND post.`post_status` IN () ORDER BY post.`post_date` DESC LIMIT 10 程序自上而下,从左到右执行,先SELECT 再LEFT JOIN 多个表,最后再WHERE 以及 ORDER BY 和 LIMIT,咋看一下也没有问题啊,但实际上很有问题。 问题分析???
再看优化后的SQL: 为了更好的比较它们究竟有何区别,需要理解explain获取参数的含义。 explain关键字含义(1)idMySQL QueryOptimizer选定的执行计划中查询的序列号,表达查询中执行select子句或操作表顺序。id值越大优先级越高,优先级越高就会先被执行。id相同,执行顺序由上至下。 (2)select_type
(3)table输出行所引用的表。显示的查询表名,如果查询使用了别名,那么这里显示的是别名,如果不涉及对数据表的操作,那么这显示为null,如果显示为尖括号括起来的就表示这个是临时表,后边的N就是执行计划的id,表示结果来自这个查询产生。如果是尖括号括起来的,与类似,也是一个临时表,表示这个结果来自于union查询的id为M,N的结果集。 (4)type从优到差的顺序如下:system->const->eq_ref->ref->fulltext->ref_or_null->index_merge->unique_subquery->index_subquery->range->index->all (5)possible_keys指出能再该表中使用哪些索引有助于查询,查询可能使用的索引都会再这里列出来。如果为空,说明没有可用的索引。 (6)key实际从possible_key选择使用的索引,如果为null,则没有使用索引。select_type为index_merge时,这里可能出现两个以上的索引,其他的select_type这里只会出现一个。很少的情况下,MySQL会选择优化不足的索引。这种情况下,可以再SELECT语句中使用USE INDEX来强制使用一个索引或者用IGNORE INDEX来强制MySQL忽略索引。 (7)key_len用于处理查询的索引长度,再不损失精确性的情况下,长度越短越好。如果是单列索引,那就整个索引长度算进去,如果是多列索引,那么查询不一定都能使用到所有的列,具体使用了多少个列的索引,这里就会计算进去,没有使用的列,这里不会计算进去。key_len只计算where条件用到的索引长度,而排序和分组就算用到了索引,也不会计算到ken_len中。 (8)ref显示索引的哪一列被使用。如果使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果条件使用了表达式或者函数,或者条件列发生内部隐式转换,这里可能会显示func。 (9)rows认为必须检查的用来返回请求数据的行数,即需要扫描的次数。 (10)extra这个列可以显示的信息很,如果出现Using filesort、Using temporary两项意味着不能使用索引,效率会受到重大影响。应尽可能对其进行优化。
理解完explain后,用explain重要参数来解释这段问题SQL: 但最后我发现优化后的SQL还算很冗余,因为作为首页展示,其实没必要这么多表关联,如果是查看详情的话还可以通过拆分,然后分段执行即可。 最终首页SQL如下(也相当于另外一种解法,优化为单表): post_author) AS display_name,post.`post_date` FROM wp_posts AS post WHERE `post_type` = ) ORDER BY `post_date` DESC LIMIT 10 另外除此之外,归档查询也是用的这段SQL,这样一来也需要优化,于是我将其分离写成不同的DAO,针对性优化(如果不优化,数据量过大也会有问题)。 问题SQL(归档,消耗8.183sec): ) AND DATE_FORMAT(`post_date`,1)">%Y年%m月') = 2020年06月 ORDER BY `post_date` DESC LIMIT ) AS post LEFT JOIN wp_users AS u ON(post.`post_author` = u.`ID`) 问题SQL再度优化(这次执行时间是55.496sec): ) AS post
LEFT JOIN wp_users AS u ON(post.`post_author` = u.`ID`)
最终优化版: SELECT DISTINCT post.`ID` AS postId,post.`post_date` FROM wp_posts AS post WHERE ID IN (SELECT `ID` FROM wp_posts WHERE `post_type` = ' AND `post_status` IN (') AND DATE_FORMAT(`post_date`,1)">') = ORDER BY `post_date` DESC) LIMIT 10 这个优化版本,我的思路是归档抽取为一个子查询条件查询和排序获取ID,获取ID这段SQL是毫秒级,然后再在外层LIMIT即可。 通用规律和方法
以上述方法为例,解决数据量大分页性能问题(解决博客系统点击尾页加载慢问题(本质上还是SQL原因,优化了下,主要利用主键索引)),优化后的代码如下: SELECT DISTINCT post.`ID` AS postId,post.`post_date` FROM wp_posts AS post JOIN (SELECT ID FROM wp_posts WHERE `post_type` = ') LIMIT 2340,1)">10) AS post_b ON(post.ID = post_b.ID) WHERE `post_type` = ') ORDER BY `post_date` DESC FAQ为什么SQL查询缓慢?通常可归纳为如下:
以我本次为例,首页之所以慢,是因为最开始那段SQL扫描行数大。等到扫描完后再关联表,再子查询。 而优化过后的扫描行数仅仅就10行,然后再关联再子查询。 两者的区别是前者是全部扫描一遍再关联再条件,后者直接根据条件过滤再关联。 子查询(内嵌查询)的执行过程是什么?由内向外处理,对应本文举的首页文章优化语句。 为什么SQL查单个字段不分页同样也是六万条数据,消耗时间却是毫秒级?因为IO的消耗(输入/输出),输出数据量大会导致吞吐量小(吞吐量与磁盘、CPU、内存相关)。 ? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |