零零客微文大全00ke.net

  • 记一次 MySQL 的慢查分析
  • 来源:高效运维

最近遇见一个 MySQL 的慢查问题,于是排查了下,这里把相关的过程做个总结。

定位原因

我首先查看了 MySQL 的慢查询日志,发现有这样一条 query 耗时非常长(大概在 1 秒多),而且扫描的行数很大(10 多万条数据,差不多是全表了):

SELECT * FROM tgdemand_demand t1
WHERE
(
t1.id IN
(
SELECT t2.demand_id
FROM tgdemand_job t2
WHERE (t2.state = 'working' AND t2.wangwang = 'abc')
)
AND
NOT (t1.state = 'needConfirm')
)
ORDER BY t1.create_date DESC

这个查询不是很复杂,首先执行一个子查询,取到任务的状态(state)是 ‘working’ 并且任务的关联人 (wangwang)是’abc’的所有需求 id(这个设计师进行中的任务对应的需求 id),然后再到主表 tgdemand_demand 中带入刚才的 id 集合,查询出需求状态(state)不是 ‘needConfirm’ 的所有需求,最后进行一个排序。

按道理子查询筛选出 id 后到主表过滤是直接使用到主键,应该是很快的啊。而且,我检查了子查询的 tgdemand_job 表的索引,where 中用到的查询条件都已经增加了索引。怎么会这样呢?

于是,我对这个 query 执行了一个 explain(输出 sql 语句的执行计划),看看 MySQL 的执行计划是怎样的。输出如下:

我们看到,第一行是 t1 表,type 是 ALL(全表扫描),rows(影响行数)是157089,没有用到任何索引;第二行是 t2 表,用到了索引。和我之前理解的执行顺序完全不一样!

为什么 MySQL 不是先执行子查询,而是对 t1 表进行了全表扫描呢?我们仔细看第二行的 select_type,发现它的值是 DEPENDENT_SUBQUERY,意思是这个子查询的查询方式依赖外层的查询。这是什么意思?

实际上,MySQL 对于这种子查询会进行改写,上面的 SQL 会被改写成下面的形式:

SELECT * FROM tgdemand_demand t1 WHERE EXISTS (
SELECT * FROM tgdemand_job t2 WHERE t1.id = t2.demand_id AND (t2.state = 'working' AND t2.wangwang = 'abc')
) AND NOT (t1.state = 'needConfirm')
ORDER BY t1.create_date DESC;

这表示,SQL 会去扫描 tgdemand_demand 表的所有数据,每条数据再传入到子查询中与表 tgdemand_job 进行关联,执行子查询,子查询根本不会先执行,而且子查询会执行 157089 次(外层表的记录数量)。还好我们的子查询加了必要的索引,不然结果会更加惨不忍睹。

这个结果真是太坑爹,而且十分违反直觉。对于慢查询,千万不要想当然,还是多多 explain,看看数据库实际上是怎么去执行的。

问题修复

既然子查询会被改写,那最简单的解决方案就是不用子查询,将内层获取需求 id 的 SQL 单独拿出来执行,取到结果后再执行一条 SQL 去获取实际的数据。大概像这样(下面的语句是不合法的,只是示意):

ids = SELECT t2.demand_id
FROM tgdemand_job t2
WHERE (t2.state = 'working' AND t2.wangwang = 'abc');

SELECT * FROM tgdemand_demand t1
WHERE
(
t1.id IN ids
AND
NOT (t1.state = 'needConfirm')
)
ORDER BY t1.create_date DESC;

说干咱就干,我找到了下面的代码(是 python 语言写的):

demand_ids = Job.objects.filter(wangwang=user['wangwang'], state='working').values_list("demand_id", flat=True)

demands = Demand.objects.filter(id__in=demand_ids).exclude(state__in=['needConfirm']).order_by('-create_date')

咦!这不是和我想得是一样的嘛?先查出需求 id(代码第一行),然后用 id 集合再去执行实际的查询(代码第二行)。为什么经过 ORM 框架的处理后产出的 SQL 就不一样了呢?

带着这个问题我搜索了一番。原来 Django 自带的 ORM 框架生成的 QuerySet 是懒执行的(lazy evaluated),我们可以将这种 QuerySet 到处传,直到需要时才会实际的执行 SQL。

比如,我们代码里面的 Job.objects.filter(wangwang=user['wangwang'], state='working').values_list("demand_id", flat=True) 这个 QuerySet 实际上并没有执行,就被作为参数传递给了 id__in,当 Demand.objects.filter(id__in=demand_ids).exclude(state__in=['needConfirm']).order_by('-create_date') 这个 QuerySet 执行时,刚才未执行的 QuerySet 才开始作为 SQL 执行,于是生成了最开始的 SQL 语句。

既然如此,我们的目的要让 QuerySet 提前执行,获得结果集。根据文档,对 QuerySet 进行循环、slice、取 len、list 转换的时候被执行。于是我将代码更改为了下面的样子:

demand_ids = list(Job.objects.filter(wangwang=user['wangwang'], state='working').values_list("demand_id", flat=True))

demands = Demand.objects.filter(id__in=demand_ids).exclude(state__in=['needConfirm']).order_by('-create_date')

终于,页面打开速度恢复正常了。

实际上,我们也可以对 SQL 进行改写来解决问题:

select * from tgdemand_demand t1, (select t.demand_id from tgdemand_job t where t.state = 'working' and t.wangwang = 'abc') t2
where t1.id=t2.demand_id and not (t1.state = 'needConfirm')
order by t1.create_date DESC

思路是去掉子查询,换用 2 个表进行 join 的方式来取得数据。这里就不展开了。

感想

框架可以提高生产率的前提是对背后的原理足够了解,不然应用很可能就会在某个时间暴露出一些隐蔽的要命问题(这些问题在小规模阶段可能根本都发现不了……)。保证应用的健壮真是个大学问,还有很多东西值得我们去探索。

参考资料

//www.cnblogs.com/zhengyun_ustc/p/slowquery3.html
//dev.mysql.com/doc/refman/5.5/en/explain-output.html
https://docs.djangoproject.com/en/1.9/ref/models/querysets/

来源:https://segmentfault.com/a/1190000004184019,作者:kohpoll

GOPS全球运维大会 2019 · 上海站,最想听的专场,你来做主

第十三届 GOPS 全球运维大会,为期两天,近 20 个专场等你来评选,扫描下方二维码,选出你心仪的专场~


扫描查看专场设置 ▼

点击,查看完整日程






















    已同步到看一看
















    发送中
















    社会

    • 



中科院研究生被刺始末
      中科院研究生被刺始末
      ↑点击上方三联生活周刊加星标! 2019年8月30日上午,北京一中院一审公开宣判中科院研究生被杀案,以故意杀人罪判处被告人周凯旋死刑,剥夺政治权利终身。 被告人周凯旋与被害人谢雕是高中同
      三联生活周刊
    • 



该不该考验爱情?
      该不该考验爱情?
      隔着薄膜的男人和女人 两性之间,只隔着一张纸。这张纸是不透明的,在纸的两边,彼此高深莫测。但是,这张纸又是一捅就破的,一旦捅破,彼此之间就再也没有秘密了。 普天下男人聚集在一起,也不能给女人下一个完
      周国平
    • 



解密长江云:这是一次凤凰涅槃式的自我革命!
      解密长江云:这是一次凤凰涅槃式的自我革命!
      文|「广电独家」刘欣 没有自主的平台,就没有主流媒体的一切,也就没有党和国家的安全。 这是一次凤凰涅槃式的自我革命。 2018年9月6日,中宣部副部长、国务院新闻办主任徐麟视察长江云平台后,确定
      广电独家
    • 



解密长江云:这是一次凤凰涅槃式的自我革命!
      解密长江云:这是一次凤凰涅槃式的自我革命!
      文|「广电独家」刘欣 没有自主的平台,就没有主流媒体的一切,也就没有党和国家的安全。 这是一次凤凰涅槃式的自我革命。 2018年9月6日,中宣部副部长、国务院新闻办主任徐麟视察长江云平台后,确定
      广电独家
    • 



琼瑶26年前台词惹争议:“别计较孩子父亲是谁好吗?”
      琼瑶26年前台词惹争议:“别计较孩子父亲是谁好吗?”
      你做我的朗读者 我做你的摆渡人 戳蓝字一键关注 摆渡人 ☾ 每晚21:39 无论你在哪里,请记得打开手机。 -1- 我真的超级羡慕比我早出生26年的人! 因为他们在我这个年纪,可以欣赏各种台词
      摆渡人
    • 



上了清华、哈佛才知道:比努力更能拉开人生差距的,是这种底层思维
      上了清华、哈佛才知道:比努力更能拉开人生差距的,是这种底层思维
      一流的学习高手,从不拼努力 - 来自十点读书 - / 11:20 ♪ 点击上方音频,收听清华、哈佛双料学霸的学习心法 最近听到一句话:世界上又轻松、又痛苦的职业,就是学生。 简直不能认同更多。
      十点读书
    • 



没有退路的创业者们,再难也要闯
      没有退路的创业者们,再难也要闯
      // 总有人嘲笑你做的白日梦,但是没有人会瞧不起一颗敢于追梦的心;总有人调侃失败者,但是没有人会瞧不起哪里失败就从哪里站起来的奋斗者…… 出于不同目的而投身创业的创业者,一定都经历过来自各方的嘲笑
      三联生活周刊
    • 



遇到成长会,好运就开启
      遇到成长会,好运就开启
      持续力 公众号ID:scalerstalk 个人微信号:escalers (可加Scalers个人微信交流) 关注 ScalersTalk 成长会 2019 年火热招募中,参见《用持续行动突破成
      ScalersTalk成长持续论
    • 



没有退路的创业者们,再难也要闯
      没有退路的创业者们,再难也要闯
      // 总有人嘲笑你做的白日梦,但是没有人会瞧不起一颗敢于追梦的心;总有人调侃失败者,但是没有人会瞧不起哪里失败就从哪里站起来的奋斗者…… 出于不同目的而投身创业的创业者,一定都经历过来自各方的嘲笑
      三联生活周刊
    • 



记一次 MySQL 的慢查分析
      记一次 MySQL 的慢查分析
      最近遇见一个 MySQL 的慢查问题,于是排查了下,这里把相关的过程做个总结。 定位原因 我首先查看了 MySQL 的慢查询日志,发现有这样一条 query 耗时非常长(大概在 1 秒多),而且扫描的
      高效运维
    • 



原创丨以色列突然空袭3国,一场危险战争正悄悄逼近!
      原创丨以色列突然空袭3国,一场危险战争正悄悄逼近!
      月饼每年吃一次,今年来点不一样,故宫出品,传统文化与月饼融合在每个细节,甄选每克食材,古法新标准,品出不同往年中秋。领券立减40,戳图抢! 一场危险战争正悄悄逼近! - 来自占豪 - / 10:35
      占豪
    • 



老妈问我有没有男朋友,竟然触发对自己创业的觉醒
      老妈问我有没有男朋友,竟然触发对自己创业的觉醒
      彭萦 改变自己主创 创业者 | 品牌人 | 性格分析师 很多事情该怎么做大家都懂但是做不到的是因为只愿意看到红火的表象和外界的认可,不愿意面对自己的真实。
      改变自己
    • 



哪些瞬间让你想结婚?
      哪些瞬间让你想结婚?
      哪些瞬间让你想结婚? 哪些瞬间让你想结婚? - 来自周国平 - 00:00 / 14:14 深深地爱一个人,你藉此所建立的不只是与这个人的联系,而且也是与整个人生的联系。爱的经历决定了人生内涵的广度
      周国平
    • 



融媒体中心不是灯光、大屏构成的秀场,而是一种新的运作方式
      融媒体中心不是灯光、大屏构成的秀场,而是一种新的运作方式
      作者单位|湖北广播电视台垄上频道 融媒体中心不只是所谓的多元分发系统,而是信息处理机制的再设计,需要流程重塑、绩考重塑、传播矩阵重塑。 笔者曾负责媒体呼叫中心好几年,后来,所工作的媒体分成了2家
      广电独家
    • 



关于亚马孙森林大火,你该知道的一些真相
      关于亚马孙森林大火,你该知道的一些真相
      气象学家们担心的是,这一次的火灭了,下一次火还会不会燃起来?如果博尔索纳罗政府不从根本上改变他的环境政策,那亚马孙的危机不可能得到根本上的解决。会有更多树木被砍倒,更多保护区被毁掉。 文|金钟
      人物
    << 16 17 18 19 20 >> 

    公众号 • 高效运维

    Flag Counter