问题背景:
博客首页随着数据量的增加,最初是几百上千的数据,访问正常,这是开发环境,当切换测试环境(通过爬虫已有数据六万多),这时候访问非常缓慢,长达一分钟。

问题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,
(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 = '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 ,

将这段sql放在sqlyog里执行,结果花费时间如下:
执行:59.204sec 总数:59.239 10行(仅仅显示10条数据)

优化后的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,
(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 = 'post_tag') AS tagName,
post.`comment_count`,post.`post_status`,post.`post_date`
FROM ( SELECT * FROM wp_posts WHERE `post_type` = 'post' AND `post_status` IN ('publish') ORDER BY `post_date` DESC LIMIT , ) 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里执行,结果花费时间如下:
执行:0.056sec 总数:0.288sec 10行(仅仅显示10条数据)

优化过后,直接是毫秒级。结果项目在测试环境下访问不卡了。

主要的改动把查询和过滤条件从最后面嵌入到主表的子查询里。

那么问题SQL为什么会这么慢?而优化过后的SQL为什么会突然一下如此迅速到毫秒级呢?

先看问题一,为什么问题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,
(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 = '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 ,

程序自上而下,从左到右执行,先SELECT 再LEFT JOIN 多个表,最后再WHERE 以及 ORDER BY 和 LIMIT,咋看一下也没有问题啊,但实际上很有问题。

问题分析???

  • 首先,我并没有使用SELECT * 而是列举我需要的字段。
  • 使用explain 关键字看问题SQL,结果如下:

    以type字段为主要的来说,表扫描方式:system>const>eq_ref>ref>range>index>all,最慢的是all,也就是全表扫描。

再看优化后的SQL:

为了更好的比较它们究竟有何区别,需要理解explain获取参数的含义。

explain关键字含义

(1)id

MySQL QueryOptimizer选定的执行计划中查询的序列号,表达查询中执行select子句或操作表顺序。id值越大优先级越高,优先级越高就会先被执行。id相同,执行顺序由上至下。

(2)select_type
  • SIMPLE(简单的select查询(不使用union及子查询))
  • PRIMARY(最外层的select查询,如果两表存在则查询,则外层的表操作为PRIMARY,内层(子查询)的操作为SUBQUERY)
  • SUBQUERY(子查询中首个SELECT(如果有多个子查询存在),不依赖于外层的表。除from子句中包含的子查询外,其他地方出现的子查询都可能是SUBQUERY)/DEPENDENT SUBQUERY(子查询中首个SELECT(如果有多个子查询存在),就依赖于外层的表)
(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
一般来说,开发人员写的SQL基本要求是eq_ref级别。

(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两项意味着不能使用索引,效率会受到重大影响。应尽可能对其进行优化。

  • distinct:在select部分使用了distinct关键字
  • using filesort:排序时无法使用到索引时,就会出现这个。常见于order by 和group by语句中。没有办法利用现有索引进行排序,需要额外排序,建议:根据排序需要,创建相应合适的索引。
  • using index:查询时不需要回表查询,直接通过索引就可以获取查询的数据。利用覆盖索引,无需回表即可取得结果数据,这种结果是好的。
  • using temporay:表示使用了临时表存储中间结果。
  • using where:表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server层进行过滤。

理解完explain后,用explain重要参数来解释这段问题SQL:
即key、type 、rows、extra。以其中的rows来看,该段sql执行之初直接就扫描28386次数。而优化后的SQL仅仅扫描10次,由此可知慢在该地方,针对次进行修改。这是问题SQL慢的根本原因。

但最后我发现优化后的SQL还算很冗余,因为作为首页展示,其实没必要这么多表关联,如果是查看详情的话还可以通过拆分,然后分段执行即可。

最终首页SQL如下(也相当于另外一种解法,优化为单表):

SELECT
DISTINCT post.`ID` AS postId,post.`post_title`,post.`post_content`,post.`post_excerpt`,
(SELECT `display_name` FROM wp_users WHERE ID = post_author) AS display_name,
post.`comment_count`,post.`post_status`,post.`post_date`
FROM wp_posts AS post
WHERE `post_type` = 'post' AND `post_status` IN ('publish')
ORDER BY `post_date` DESC
LIMIT ,

另外除此之外,归档查询也是用的这段SQL,这样一来也需要优化,于是我将其分离写成不同的DAO,针对性优化(如果不优化,数据量过大也会有问题)。

问题SQL(归档,消耗8.183sec):

SELECT
DISTINCT post.`ID` AS postId,post.`post_title`,post.`post_content`,post.`post_excerpt`,u.`display_name`,
post.`comment_count`,post.`post_status`,post.`post_date`
FROM ( SELECT * FROM wp_posts WHERE `post_type` = 'post' AND `post_status` IN ('publish')
AND DATE_FORMAT(`post_date`, '%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):

SELECT
DISTINCT post.`ID` AS postId,post.`post_title`,post.`post_content`,post.`post_excerpt`,u.`display_name`,
post.`comment_count`,post.`post_status`,post.`post_date`
FROM ( SELECT * FROM wp_posts WHERE `post_type` = 'post' AND `post_status` IN ('publish')
AND DATE_FORMAT(`post_date`, '%Y年%m月') = '2020年06月'
ORDER BY `post_date` DESC LIMIT , ) AS post
LEFT JOIN wp_users AS u ON(post.`post_author` = u.`ID`)

最终优化版:

SELECT
DISTINCT post.`ID` AS postId,post.`post_title`,post.`post_content`,post.`post_excerpt`,
(SELECT `display_name` FROM wp_users WHERE ID = post_author) AS display_name,
post.`comment_count`,post.`post_status`,post.`post_date`
FROM wp_posts AS post WHERE ID IN
(SELECT `ID` FROM wp_posts WHERE `post_type` = 'post' AND `post_status` IN ('publish') AND DATE_FORMAT(`post_date`, '%Y年%m月') = '2020年06月' ORDER BY `post_date` DESC)
LIMIT ,

这个优化版本,我的思路是归档抽取为一个子查询条件查询和排序获取ID,获取ID这段SQL是毫秒级,然后再在外层LIMIT即可。

通用规律和方法

  • 通过explain关键字理解SQL走向和慢的原因(explain中的id可以了解sql是如何执行的)
  • 学会拆分,一分为二写(以查看文章详情为例,可分为两部分,一部分为获取详情,另外一部分获取文章对应的分类或标签,这样一来sql基本上都可以确保为eq_ref级别且毫秒级)
  • 合理使用子查询(例如归档这部分查id,拿获取的id作为where查询条件,背后的原理走主键索引,那么为什么主键索引快,因为主键索引比普通索引快是因为主键索引只检索一次)

以上述方法为例,解决数据量大分页性能问题(解决博客系统点击尾页加载慢问题(本质上还是SQL原因,优化了下,主要利用主键索引)),优化后的代码如下:

SELECT DISTINCT post.`ID` AS postId,post.`post_title`,post.`post_content`,post.`post_excerpt`,
(SELECT `display_name` FROM wp_users WHERE ID = post_author) AS display_name, post.`comment_count`,post.`post_status`,post.`post_date`
FROM wp_posts AS post JOIN (SELECT ID FROM wp_posts WHERE `post_type` = 'post' AND `post_status` IN ('publish') LIMIT ,) AS post_b ON(post.ID = post_b.ID)
WHERE `post_type` = 'post' AND `post_status` IN ('publish') ORDER BY `post_date` DESC

FAQ

为什么SQL查询缓慢?

通常可归纳为如下:

  • 没有索引或没有用到索引
  • I/O吞吐量小
  • 内存不足
  • 网络速度慢
  • 查询出的数据量过大
  • 锁或死锁
  • 返回不必要的行和列

以我本次为例,首页之所以慢,是因为最开始那段SQL扫描行数大。等到扫描完后再关联表,再子查询。

而优化过后的扫描行数仅仅就10行,然后再关联再子查询。

两者的区别是前者是全部扫描一遍再关联再条件,后者直接根据条件过滤再关联。

子查询(内嵌查询)的执行过程是什么?

由内向外处理,对应本文举的首页文章优化语句。

为什么SQL查单个字段不分页同样也是六万条数据,消耗时间却是毫秒级?

因为IO的消耗(输入/输出),输出数据量大会导致吞吐量小(吞吐量与磁盘、CPU、内存相关)。

SQL优化之博客案例的更多相关文章

  1. SQL Server 学习博客分享列表(应用式学习 + 深入理解)

    SQL Server 学习博客分享列表(应用式学习 + 深入理解) 转自:https://blog.csdn.net/tianjing0805/article/details/75047574 SQL ...

  2. Github、Jekyll 搭建及优化静态博客方法指南

    尝试自己写 Blog 的人,一般会经历三个阶段. 第一阶段,刚接触 Blog,觉得很新鲜,试着选择一个免费空间来写. 第二阶段,发现免费空间限制太多,就自己购买域名和空间,搭建独立博客. 第三阶段,觉 ...

  3. sql server 老外博客

    Aaron Bertrand Grant Fritchey Brent Ozar Thomas LaRock Pinal Dave Phil Factor SQL Skills w/ Paul Ran ...

  4. SQL SERVER 技术博客 外文

    https://www.sqlskills.com/blogs/paul/capturing-io-latencies-period-time/ http://www.sqlskills.com/bl ...

  5. nodeJs学习-19 个人博客案例-(1)数据字典

    智能社视频27.28 数据字典: 定义: url 300字 admin_table 管理员用户表 ID username varchar(32) password varchar(32) banner ...

  6. nodeJs学习-17 博客案例

    源码:智能社视频20节课件 const express=require('express'); const static=require('express-static'); const cookie ...

  7. sql优化个人总结(全)

    sql优化总结--博客 第一次自己写博客,以后要坚持每掌握一个技能点,就要写一篇博客出来,做一个不满足于一个只会写if...else的程序员. 最近三个月入职了一家新的公司,做的是CRM系统,将公司多 ...

  8. 基于oracle的sql优化

    [基于oracle的sql优化] 基于oracle的sql优化 [博主]高瑞林 [博客地址]http://www.cnblogs.com/grl214 一.编写初衷描述 在应有系统开发初期,由于数据库 ...

  9. 文顶顶iOS开发博客链接整理及部分项目源代码下载

    文顶顶iOS开发博客链接整理及部分项目源代码下载   网上的iOS开发的教程很多,但是像cnblogs博主文顶顶的博客这样内容图文并茂,代码齐全,示例经典,原理也有阐述,覆盖面宽广,自成系统的系列教程 ...

  10. 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!

    本文来自:http://blog.csdn.net/ouyang_peng/article/details/11358405 今天看到一篇文章,收藏了很多大牛的博客,在这里分享一下 谦虚的天下 柳志超 ...

随机推荐

  1. [Leetcode][JAVA] Recover Binary Search Tree (Morris Inorder Traversal)

    Two elements of a binary search tree (BST) are swapped by mistake. Recover the tree without changing ...

  2. 设置EDIUS字幕时有哪些要注意的

    我们在用EDIUS添加字幕,有时候可能会遇到以下麻烦.例如有的字体在EDIUS中找不到,诗歌的排版问题还有怎么给字幕加光效等等.今天小编主要来给大家解决这三个问题,让你们知道EDIUS字幕设置时应该注 ...

  3. PHP Startup: Unable to load dynamic library

    昨天帮一朋友配置服务器结果发现apache日志中有PHP Warning: PHP Startup: Unable to load dynamic library 提示了,然后调试数据库连接发现提示C ...

  4. 怎样通过iPhone Safari 来安装测试版ipa

    http://www.cocoachina.com/bbs/read.php?tid=94101# <?xml version="1.0" encoding="UT ...

  5. document.all用法

    document.all用法 一. document.all是页面内所有元素的一个集合.例如:       document.all(0)表示页面内第一个元素二.document.all可以判断浏览器 ...

  6. 无法解析属性“mode”的值。错误为: 枚举值必须是以下各值中的一个: RemoteOnly, On, Off。

    Off首字母要大写,注意大小写 <customErrors mode="Off">      <error statusCode="404" ...

  7. PHP iconv()编码转换函数用法示例

    PHP iconv()字符编码转换函数的用法,iconv()函数,在php5中是内置的,语法格式:iconv("UTF- 8","GB2312//IGNORE" ...

  8. HTML5简介、视频

    HTML5 建立的一些规则: 新特性应该基于 HTML.CSS.DOM 以及 JavaScript. 减少对外部插件的需求(比如 Flash) 更优秀的错误处理 更多取代脚本的标记 HTML5 应该独 ...

  9. 《STL源代码分析》---stl_heap.h读书笔记

    Heap堆的数据结构是经常使用,Heap它还能够存储元件的.但STL并且不提供Heap集装箱.仅仅提供信息Heap算术运算.只支持RandomAccessIterator该容器可以被用作Heap集装箱 ...

  10. Redis压缩列表

    此篇文章是主要介绍Redis在数据存储方面的其中一种方式,压缩列表.本文会介绍1. 压缩列表(ziplist)的使用场景 2.如何达到节约内存的效果?3.压缩列表的存储格式 4. 连锁更新的问题  5 ...