专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

MySQL的GroupBy深度优化,真是绝了

  导读
  当我们交友平台在线上运行一段时间后,为了给平台用户在搜索好友时,在搜索结果中推荐并置顶他感兴趣的好友,这时候,我们会对用户的行为做数据分析,根据分析结果给他推荐其感兴趣的好友。
  这里,我采用最简单的SQL分析法:对用户过去查看好友的性别和年龄进行统计,按照年龄进行分组得到统计结果。依据该结果,给用户推荐计数最高的某个性别及年龄的好友。
  那么,假设我们现在有一张用户浏览好友记录的明细表t_user_view,该表的表结构如下:  CREATE TABLE `t_user_view` (   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT "自增id",   `user_id` bigint(20) DEFAULT NULL COMMENT "用户id",   `viewed_user_id` bigint(20) DEFAULT NULL COMMENT "被查看用户id",   `viewed_user_sex` tinyint(1) DEFAULT NULL COMMENT "被查看用户性别",   `viewed_user_age` int(5) DEFAULT NULL COMMENT "被查看用户年龄",   `create_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3),   `update_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),   PRIMARY KEY (`id`),   UNIQUE KEY `idx_user_viewed_user` (`user_id`,`viewed_user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  为了方便使用SQL统计,见上面的表结构,我冗余了被查看用户的性别和年龄字段。
  我们再来看看这张表里的记录:
  现在结合上面的表结构和表记录,我以 user_id=1 的用户为例,分组统计该用户查看的年龄在18 ~ 22之间的女性用户的数量: SELECT viewed_user_age as age, count(*) as num FROM t_user_view WHERE user_id = 1 AND viewed_user_age BETWEEN 18 AND 22 AND viewed_user_sex = 1 GROUP BY viewed_user_age
  得到统计结果如下:
  可见:  该用户查看年龄为18的女性用户数为2  该用户查看年龄为19的女性用户数为1  该用户查看年龄为20的女性用户数为3
  所以, user_id=1 的用户对年龄为20的女性用户更感兴趣,可以更多推荐20岁的女性用户给他。
  如果此时,t_user_view这张表的记录数达到千万规模,想必这条SQL的查询效率会直线下降,为什么呢?有什么办法优化呢?
  想要知道原因,不得不先看一下这条SQL执行的过程是怎样的?  Explain
  我们先用 explain 看一下这条SQL: EXPLAIN SELECT viewed_user_age as age, count(*) as num FROM t_user_view WHERE user_id = 1 AND viewed_user_age BETWEEN 18 AND 22 AND viewed_user_sex = 1 GROUP BY viewed_user_age
  执行完上面的 explain 语句,我们得到如下结果:
  在 Extra 这一列中出现了三个Using ,这3个Using 代表了《导读》中的groupBy 语句分别经历了3个执行阶段: Using where:通过搜索可能的 idx_user_viewed_user 索引树定位到满足部分条件的viewed_user_id ,然后,回表继续查找满足其他条件的记录 Using temporary:使用临时表暂存待 groupBy 分组及统计字段信息 Using filesort:使用 sort_buffer 对分组字段进行排序
  这3个阶段中出现了一个名词: 临时表 。这个名词我在《MySQL分表时机:100w?300w?500w?都对也都不对!》一文中有讲到,这是MySQL连接线程可以独立访问和处理的内存区域,那么,这个临时表长什么样呢?
  下面我就先讲讲这张MySQL的临时表,然后,结合上面提到的3个阶段,详细讲解《导读》中SQL的执行过程。   临时表
  我们还是先看看《导读》中的这条包含 groupBy 语句的SQL,其中包含一个分组字段viewed_user_age 和一个统计字段count(*) ,这两个字段是这条SQL中统计所需的部分,如果我们要做这样一个统计和分组,并把结果固化下来,肯定是需要一个内存或磁盘区域落下第一次统计的结果,然后,以这个结果做下一次的统计,因此,像这种存储中间结果,并以此结果做进一步处理的区域,MySQL叫它临时表 。
  刚刚提到既可以将中间结果落在内存,也可以将这个结果落在磁盘,因此,在MySQL中就出现了两种临时表: 内存临时表 和磁盘临时表 。 内存临时表
  什么是内存临时表?在早期数据量不是很大的时候,以存储分组及统计字段为例,那么,基本上内存就可以完全存放下分组及统计字段对应的所有值,这个存放大小由 tmp_table_size 参数决定。这时候,这个存放值的内存区域,MySQL就叫它内存临时表。
  此时,或许你已经觉得MySQL将中间结果存放在内存临时表,性能已经有了保障,但是,在《MySQL分表时机:100w?300w?500w?都对也都不对!》中,我提到过内存频繁的存取会产生碎片,为此,MySQL设计了一套新的内存分配和释放机制,可以减少甚至避免临时表内存碎片,提升内存临时表的利用率。
  此时,你可能会想,我讲了用户态的内存分配器:ptmalloc和tcmalloc,无论是哪个分配器,它的作用就是避免用户进程频繁向Linux内核申请内存空间,造成CPU在用户态和内核态之间频繁切换,从而影响内存存取的效率。用它们就可以解决内存利用率的问题,为什么MySQL还要自己搞一套?
  或许MySQL的作者觉得无论哪个内存分配器,它的实现都过于复杂,这些复杂性会影响MySQL对于内存处理的性能,因此,MySQL自身又实现了一套内存分配机制: MEM_ROOT 。它的内存处理机制相对比较简单,内存临时表的分配就是采用这样一种方式。
  下面,我就以《导读》中的SQL为例,详细讲解一下分组统计是如何使用 MEM_ROOT 内存分配和释放机制的?Spring Boot 学习笔记,这个分享给你,太全了。 MEM_ROOT
  我们先看看 MEM_ROOT 的结构,MEM_ROOT 设计比较简单,主要包含这几部分,如下图:
  free:一个单向链表,链表中每一个单元叫 block ,block 中存放的是空闲的内存区,每个block 包含3个元素: left: block 中剩余的内存大小 size: block 对应内存的大小 next:指向下一个 block 的指针
  如上图, free 所在的行就是一个free 链表,链表中每个箭头相连的部分就是block ,block 中有left 和 size ,每个block 之间的箭头就是next 指针
  used:一个单向链表,链表中每一个单元叫 block ,block 中存放已使用的内存区,同样,每个block 包含上面3 个元素
  min_malloc:控制一个  block  剩余空间还有多少的时候从free 链表移除,加入到used 链表中
  block_size: block 对应内存的大小
  block_num: MEM_ROOT  管理的block 数量
  first_block_usage: free 链表中第一个block 不满足申请空间大小的次数
  pre_alloc:当释放整个 MEM_ROOT 的时候可以通过参数控制,选择保留pre_alloc 指向的block
  下面我就以《导读》中的分组统计SQL为例,看一下 MEM_ROOT 是如何分配内存的? 分配
  初始化 MEM_ROOT ,见上图:min_malloc = 32block_num = 4first_block_usage = 0pre_alloc = 0block_size = 1000err_handler = 0free = 0used = 0  申请内存,见上图:由于初始化 MEM_ROOT 时,free = 0 ,说明free 链表不存在,故向Linux内核申请4个大小为1000/4=250 的block ,构造一个free 链表,如上图,链表中包含4个block  ,结合前面free 链表结构的说明,每个block 中size 为250,left 也为250 分配内存,见上图:(1) 遍历 free 链表,从free 链表头部取出第一个block ,如上图向下的箭头(2) 从取出的 block 中划分220 大小的内存区,如上图向右的箭头上面-220 ,block 中的left 从250 变成30 (3) 将划分的 220 大小的内存区分配给SQL中的groupby 字段viewed_user_age 和统计字段count(*) ,用于后面的统计分组数据收集到该内存区(4) 由于第(2)步中,分配后的 block 中的left 变成30 ,30 < 32 ,即小于第(1)步中初始化的min_malloc ,所以,结合上面min_malloc 的含义的讲解,该block 将插入used 链表尾部,如上图底部,由于used 链表在第(1)步初始化时为0,所以,该block 插入used 链表的尾部,即插入头部 释放
  下面还是以《导读》中的分组统计为例,我们再来看一下 MEM_ROOT 是如何释放内存的?
  如上图, MEM_ROOT 释放内存的过程如下: 遍历 used 链表中,找到需要释放的block ,如上图,block(30,250) 为之前已分配给分组统计用的block  将 block(30,250) 中的left + 220 ,即30 + 220 = 250 ,释放该block 已使用的220 大小的内存区,得到释放后的block(250,250)  将 block(250,250) 插入free 链表尾部,如上图曲线箭头部分
  通过 MEM_ROOT 内存分配和释放的讲解,我们发现MEM_ROOT 的内存管理方式是在每个Block 上连续分配,内部碎片基本在每个Block 的尾部,由min_malloc 成员变量控制,但是min_malloc 的值是在代码中写死的,有点不够灵活。所以,对一个block 来说,当left 小于min_malloc ,从其申请的内存越大,那么block 中的left 值越小,那么,该block 的内存利用率越高,碎片越少,反之,碎片越多。这个写死是MySQL的内存分配的一个缺陷。 磁盘临时表
  当分组及统计字段对应的所有值大小超过 tmp_table_size 决定的值,那么,MySQL将使用磁盘来存储这些值。这个存放值的磁盘区域,MySQL叫它磁盘临时表。
  我们都知道磁盘存取的性能一定比内存存取的性能差很多,因为会产生磁盘IO,所以,一旦分组及统计字段不得不写入磁盘,那性能相对是很差的,所以,我们尽量调大参数 tmp_table_size ,使得组及统计字段可以在内存临时表中处理。 执行过程
  无论是使用内存临时表,还是磁盘临时表,临时表对组及统计字段的处理的方式都是一样的。《导读》中我提到想要优化《导读》中的那条SQL,就需要知道SQL执行的原理,所以,下面我就结合上面讲解的临时表的概念,详细讲讲这条SQL的执行过程,见下图:
  创建临时表 temporary ,表里有两个字段viewed_user_age 和count(*) ,主键是viewed_user_age ,如上图,倒数第二个框temporary 表示临时表,框中包含两个字段viewed_user_age 和count(*) ,框内就是这两个字段对应的值,其中viewed_user_age 就是这张临时表的主键 扫描表辅助索引树 idx_user_viewed_user ,依次取出叶子节点上的id 值,即从索引树叶子节点中取到表的主键id。如上图中的idx_user_viewed_user 框就是索引树,框右侧的箭头表示取到表的主键id 根据主键id到聚簇索引 cluster_index 的叶子节点中查找记录,即扫描cluster_index 叶子节点:(1) 得到一条记录,然后取到记录中的 viewed_user_age 字段值。如上图,cluster_index 框,框中最右边的一列就是viewed_user_age 字段的值(2) 如果临时表中没有主键为 viewed_user_age 的行,就插入一条记录 (viewed_user_age , 1)。如上图的temporary 框,其左侧箭头表示将cluster_index 框中的viewed_user_age 字段值写入temporary 临时表(3) 如果临时表中有主键为 viewed_user_age 的行,就将viewed_user_age 这一行的count(*) 值加 1。如上图的temporary 框 遍历完成后,再根据字段 viewed_user_age 在sort_buffer 中做排序,得到结果集返回给客户端。如上图中的最右边的箭头,表示将temporary 框中的viewed_user_age 和count(*) 的值写入sort_buffer ,然后,在sort_buffer 中按viewed_user_age 字段进行排序
  通过《导读》中的SQL的执行过程的讲解,我们发现该过程经历了4个部分:idx_user_viewed_user、cluster_index、temporary和sort_buffer,对比上面explain的结果,其中前2个就对应结果中的Using where,temporary对应的是Using temporary,sort_buffer对应的是Using filesort。   优化方案
  此时,我们有什么办法优化这条SQL呢?
  既然这条SQL执行需要经历4个部分,那么,我们可不可以去掉最后两部分呢,即去掉temporary和sort_buffer?Spring Boot 学习笔记,这个分享给你,太全了。
  答案是可以的,我们只要给SQL中的表 t_user_view 添加如下索引: ALTER TABLE `t_user_view` ADD INDEX `idx_user_age_sex` (`user_id`, `viewed_user_age`, `viewed_user_sex`);
  你可以自己尝试一下哦!用 explain 康康有什么改变! 小结
  本章围绕《导读》中的分组统计SQL,通过 explain 分析SQL的执行阶段,结合临时表的结构,进一步剖析了SQL的详细执行过程,最后,引出优化方案:新增索引,避免临时表对分组字段的统计,及sort_buffer 对分组和统计字段排序。
  当然,如果实在无法避免使用临时表,那么,尽量调大 tmp_table_size ,避免使用磁盘临时表统计分组字段。 思考题
  为什么新增了索引 idx_user_age_sex 可以避免临时表对分组字段的统计,及sort_buffer 对分组和统计字段排序?
  提示:结合索引查找的原理。  来源:www.juejin.cn/post/6957696820621344775

行业资讯邓老金方ampampampEDG限定款薄荷糖震撼上市文杨广图视频受访者提供随着电竞时代的到来,电竞圈成为了品牌们争夺年轻人的新洼地,越来越多的品牌开始挖掘并布局电竞营销。其中,作为国潮养生品牌的邓老金方,今年正式成为EDG全新赞助商一梦江湖玩家花样百出,新惊喜惊艳众人,网友江湖牛人多在一梦江湖(原楚留香手游)中大家总能发现很多有趣的人或事,有时候随便在金顶玩耍也能碰见一些牛人,他们有的能把玩具玩出各种花样,有的是乐器大神,有的是社交达人,今天我们就来聊聊一梦江一拳超人中,阿修罗处于生物进化的顶端,没有琦玉,英雄协会能击败阿修罗吗?进化之家提出了生物的限制器进化理论,基诺斯博士据此创造了阿修罗,而且S级英雄僵尸男也是他的作品之一,然而因为阿修罗的人格设定出现偏差,所以他仍是失败品,不过他的生物体质是顶级的,达腾讯计划增持Supercell股份腾讯子公司Funcom开发沙丘游戏腾讯计划增持Supercell股份,估值110亿美元近日有知情人士透露,腾讯目前正准备以约110亿美元的估值,增持业界偶像芬兰手游厂商Supercell的股份,较当初102亿美元的赛训杯天霸暂居榜首!双天战队决赛圈争雄,新军XCG碰瓷下城区PGL冬季赛训杯决赛已经开赛十六支来自PCL联赛和次级联赛的队伍,将会在四天的比赛中决出最终的冠军,这次赛训杯决赛采用积分制和FPP模式,每个队需每天参加六场比赛,一共打满二十四场永远的神回归LPL!Doinb宣布Uzi复出小明选择Gala放弃小狗永远的神回归lPLDoinb宣布Uzi正式复出小明选择Gala放弃小狗自从冬季转会期开始后,各种大瓜满天飞,看的LPL观众应接不暇。但其中关于Uzi复出的消息,总能最先引起观众的注LOL公认陷阱装备诞生!影焰法穿造假,一套技能被吞上百伤害各位英雄联盟玩家们好,我是灰哥,今天要跟大家聊一聊影焰这件装备。基础法强仅次于帽子又附带高额法穿,影焰上架后很快就受到了法师玩家的追捧,如今有很多人都习惯第二件就出它,但其实这件装一回合欠42费还能斩杀?这套路太骚了全自动42血的斩杀你可曾听过?理论上的无限斩杀你又是否听说过?这些都要拜一张牌所赐血怒者科尔拉克早在预览季,以及主播试玩期间,血怒者这张牌还是挺被大家看好的,但等到新版本上线之后,CSGOBLAST总决赛开启,8支队伍角逐100万美金,QUQU将全程解说12月14日到19日,CSGO盛宴即BLAST全球总决赛将会持续展开,虎牙将对本次赛事进行全程放送。作为IEM冬季赛结束后的又一赛事,BLAST可谓算是年终盛宴了。8支队伍齐聚哥本秦殇图文攻略1入门介绍声明本攻略是在现有攻略和研究的基础上整理完善而成的,非常感谢流浪铁匠陈技术微笑大神mkv我行我素群管理员少帅等玩家分享的游戏技术资料。为避免不必要的误会,特此说明。目录第一部分单人早期飞行员执行任务有多难?看ATLAS的飞行器就知道了在第一次世界大战这场号称绞肉厂的战争里,如果说陆军参战是九死一生的话,那么空军参战便是十死无生,在当时飞机的技术并不发达,有时候都不用被对手攻击,自己的毛病都有可能会让它成为一片废
LCK比赛现场两位coser火了,一位盲僧一位扎克,观众直接看懵了,你觉得如何?LOL联赛经过几年的发展,如今已成为了全球范围内最火的电竞赛事,很多人就算没时间玩游戏,也会抽出时间看看LOL比赛,也许这就是LOL的魅力所在吧。前不久,LCK赛区一场KZ对阵DW熊猫直播愿守护IG到最后一刻!网友对此举褒贬不一,你怎么看?大家都知道熊猫直播和IG都属于王思聪的事业,虽然坊间传说王思聪早就套现离开了熊猫直播,也有人传闻说是360实际控制熊猫tv,但是熊猫tv和IG的关系一直相当的好。而早年去熊猫直播的央视主持人沉迷游戏没空撸猫,猫咪一脸生无可恋3月9日,央视走近科学主持人,同时也是游戏高手张腾岳,他在凌晨1点钟发布了自己打游戏的场景。喜爱他栏目的网友们纷纷调笑,走进科学变成了走进鬼泣,张老师不来做游戏直播界一哥吗?只要您王者荣耀3月12号要更新?碎片商城都更新了什么?大家好,这里是高进说游戏。王者荣耀官方在前两天发布了关于对购买了2019年情人节限定皮肤的玩家在白色情人节补偿以及可以退款的消息,白色情人节即3月14日,补偿的内容就是给拥有两款情明日之后玩家用648抽到帝皇铠甲!幸存者帝国交给你了!欢迎诸位小伙伴们来到本期游戏君开讲的明日之后生存那点事儿今天呢,咱们聊聊玩家用648抽到帝皇铠甲利维坦的远方亲戚以及新地图海岛树精BOSS的弱点等有趣的玩家游戏发现得,废话不多说啦起初不被玩家看好,现在碾压98K的狙击枪,吃鸡网友真香!Kar98k毛瑟步枪属于世界名枪之一,这几年的吃鸡游戏的大火,也将98K的热度再度推向了高潮。这一把二战时期德国纳粹军队的制式步枪,从1935年服役直到二战结束,是公认的最好旋转后孙悟空我不敢A他,后羿我也不敢A,吕布他来我面前试试大家好,我是瞬瞬游戏,今天我又来给大家讲讲游戏里面的事情,相信很多玩家都知道,在王者荣耀里有很多英雄都相生相克,有些英雄就算六神装的,依旧打不过才三件套的英雄,让很多玩家只用一个英最长寿国产游戏,火了整整13年!现出一大招,更引2亿玩家回归!近几年,中国游戏行业一直蓬勃发展,从最初的单机到如今的各种3D大作,更新换代速度可谓是如火箭一般!但即使在这样的大环境下,还是有一些经典网游历经10多年依旧长盛不衰,比如网龙旗下的LPL放弃比赛的AD诞生,六神装大舅子见队友仅两件套后,EZ跳脸结束游戏,怎么回事?怎么说呢?有人说是大舅子认错人了,以为吸血鬼是队友,所以跳脸打加里奥。有人说是大舅子没想到吸血鬼在旁边。还有人就说大舅子是感觉赢下比赛无望,C不动了,就直接送了一波了。我们来看一下完美世界手游口碑暴跌,腾讯丝毫不慌,只因手里还有4款大杀器!由完美世界诛仙手游原班人马打造的完美世界手游,日期上线后一度冲上了ios畅销榜首的位置,但是与此同时,在目前最具公信力的手游平台TapTap上,游戏评分却从测试前的8。7暴跌至2分登峰造极的游戏!2019年绝对值得一玩的好游戏!新人入坑需谨慎各位玩家朋友如果会点进来看这篇评测,那想必不需要笔者太多废话去介绍了。作为动作游戏这个品类的代名词之一,鬼泣系列游戏在很长的一段时间里,都是动作游戏界天花板级别的存在,比它好玩的游
友情链接:快好知快生活快百科快传网中准网文好找聚热点快软件