SQL性能优化实践

栏目: 数据库 · 发布时间: 4年前

内容简介:记录一次优化SQL查询的经历。需求:统计历史某个时间段内每个新增设备在未来30天中的活跃情况

记录一次优化 SQL 查询的经历。

SQL性能优化实践

需求:统计历史某个时间段内每个新增设备在未来30天中的活跃情况

问题:原SQL执行特别慢,原因在于通过 device_id 去关联 dws_base.device_info_day 的方式效率不高,它会为每个device_id去 dws_base.device_info_day 执行一次查询,每次的查询时间区间为自注册日 first_day 后的30天,所以导致查询时间特别长

-- 原SQL
SET mapreduce.job.queuename=root.hive;
select a.dt,count(distinct a.device_id)new_device_nums,count( case when b.device_id is null then a.device_id end)liushi_device_nums
from 
(select device_id,from_unixtime(unix_timestamp(first_day),'yyyyMMdd')dt 
from dws_base.device_info_all 
where first_day>="2018-12-17 00:00:00"
and first_day<="2018-12-23 00:00:00")a  
left join
(select distinct b.device_id,from_unixtime(unix_timestamp(first_day),'yyyyMMdd')dt  
from dws_base.device_info_all a 
left join dws_base.device_info_day b 
on a.device_id = b.device_id 
where first_day>="2018-12-17 00:00:00"
and first_day<="2018-12-23 00:00:00"
and b.dt >= from_unixtime(unix_timestamp(a.first_day)+1*24*60*60,'yyyyMMdd')
and b.dt <= from_unixtime(unix_timestamp(a.first_day)+30*24*60*60,'yyyyMMdd')
 )b
on a.device_id=b.device_id
and a.dt=b.dt
group by a.dt
order by a.dt;

思路: device_info_all (记为a)表中有设备id和设备首次登陆时间, dws_base.device_info_day (记为b)表中有设备id和只记录其当天的登陆的一条记录。 要统计a表中新增设备的登陆情况,则需要通过关联b表才能出来,但这里有一个问题,a表中设备的登陆记录可能不止一条,所以设备登陆日距离首次登陆日的日期差值date_diff会有多个, date_diff 如何计算呢?如果按照之前的SQL,逻辑上是正确的,但是执行起来会消耗大量的资源,因为它会为每个设备id去b表中执行一次子查询,最终会导致内存溢出而执行不成功。所以我想了一个比较笨的办法,举个例子,如果我们要考虑 "2018-12-17 00:00:00""2018-12-23 00:00:00" 之间新注册登录的设备在在未来30天的活跃情况,

(SELECT distinct device_id, from_unixtime(unix_timestamp(first_day),'yyyyMMdd') dt FROM dws_base.device_info_all 
where first_day>="2018-12-17 00:00:00" and first_day<="2018-12-23 00:00:00" and device_id IS NOT NULL AND device_id <> "") as a

那么我们 仅仅需要在b表中把设备登陆日志的时间窗口限制在 "2018-12-18 00:00:00""2018-01-22 00:00:00" 之间 即可,

(select * FROM dws_base.device_info_day 
where dt >= from_unixtime(unix_timestamp("2018-12-17 00:00:00")+1*24*60*60,'yyyyMMdd') 
  and dt <= from_unixtime(unix_timestamp("2018-12-23 00:00:00")+30*24*60*60,'yyyyMMdd')) b

然后计算a表中设备id首次登陆日期与b表中该id在未来30多天登陆记录日期的日期差 date_diff 。这样,便保证了 "2018-12-17 00:00:00""2018-12-23 00:00:00" 之间的设备完整地被限制在30天的窗口内,虽然除了23号,其他天数的考察窗口都超过了30天,但我们可以通过 date_diff 参数来区分,留存和流失用户。

最后我们拿到的数据有4列,分别是设备id( device_id )、设备首次登陆日期( first_day )、设备日常登陆日期( login_day )、设备登陆时距离首次登陆的时间( date_diff )到底当设备的 date_diff 符合什么样的条件才能被判定为活跃呢?总结了一下,大体分为4种情形:

  1. 设备自首次登陆之后就再也没有登陆过,那它的 date_diff 就都为0,所以该设备被判定为 流失
  2. 设备自首次登陆之后的30天内没有登陆记录,但是30天后有登陆记录,此时的 date_diff{31,35,38...} 都大于30,但该设备判定为 流失
  3. 设备自首次登陆之后的30天内有登陆记录,并且30天后也有登陆记录,此时的 date_diff{0,5,16,31...} 有大于30的有小于30的,该设备判定为 留存
  4. 设备自首次登陆之后的30天内有登陆记录,并且30天后也有登陆记录,此时的 date_diff{5,16,31...} 有大于30的有小于30的,该设备判定为 留存

现在的任务就是如何写出一个条件准确地筛选出流失用户和留存用户,一开始我的设置的条件是 min(date_diff)<=30 and min(date_diff)>0 ,也就是只要当date_diff的最小值小于等于30且大于0就判定为留存用户,但是SQL执行后的结果却很奇怪,只有时间窗口的第一天(2018-12-17)数据是正常的,其他的日期则留存设备数量明显偏少,很不符合实际情况。后来才发现,如果是 2018-12-18 ,因为设备登陆日志的窗口在 "2018-12-18 00:00:00""2018-01-22 00:00:00" ,那么此时的date_diff会有等于0的情形,而我们的 min(date_diff)>0 把这部分数据都过滤掉了(对应情形3),所以导致数据不正常。然后我把过滤条件修改为 min(date_diff)<=30 and avg(date_diff)>0 ,这样就完美地把4种情形区分开来了。

-- 优化后的sql
SET mapreduce.job.queuename=root.hive;
select d.first_day dt,count(distinct d.device_id)new_device_nums,sum(label)liucun_device_nums
FROM 
(select c.device_id,c.first_day, 
case when (min(date_diff)<=30 and avg(date_diff)>0) then 1 -- 防止过滤掉date_diff为0的数据
-- else date_diff=0 and min(date_diff)>30 then 0 
else 0 end as label
FROM 
(select a.device_id,a.dt first_day,b.dt login_day,
datediff(from_unixtime(unix_timestamp(b.dt,'yyyyMMdd'),'yyyy-MM-dd'),from_unixtime(unix_timestamp(a.dt,'yyyyMMdd'),'yyyy-MM-dd')) as date_diff
FROM 
(SELECT distinct device_id, from_unixtime(unix_timestamp(first_day),'yyyyMMdd') dt FROM dws_base.device_info_all 
where first_day>="2018-12-17 00:00:00" and first_day<="2018-12-23 00:00:00" and device_id IS NOT NULL AND device_id <> "") as a
left join
(select * FROM dws_base.device_info_day 
where dt >= from_unixtime(unix_timestamp("2018-12-17 00:00:00")+1*24*60*60,'yyyyMMdd') 
  and dt <= from_unixtime(unix_timestamp("2018-12-23 00:00:00")+30*24*60*60,'yyyyMMdd')) b 
on a.device_id=b.device_id) 
as c group by c.device_id,c.first_day) 
as d
group by d.first_day
order by d.first_day;

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

莱昂氏UNIX源代码分析

莱昂氏UNIX源代码分析

(澳)John Lions / 尤晋元 / 机械工业出版社 / 2000-7-1 / 49.00

本书由上、下两篇组成。上篇为UNIX版本6的源代码,下篇是莱昂先生对UNIX操作系统版本6源代码的详细分析。本书语言简洁、透彻,曾作为未公开出版物广泛流传了二十多年,是一部杰出经典之作。本书适合UNIX操作系统编程人员、大专院校师生学习参考使用。一起来看看 《莱昂氏UNIX源代码分析》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具