MySQL Join与子查询性能对比:获取符合条件的用户首单日期
MySQL Join与子查询性能对比:选最优方案获取用户首单日期
我们需要从tb_order和tb_user表中,获取满足**订单状态为1(已完成)或2(已取消)**的用户首单日期,输出格式为order_id、user_id、user_1st_order_date,时间字段均为Unix时间戳。当前有两种实现方案,需选出性能最优的用于每日更新Tableau Online数据源。
Option 1: JOIN方案
原写法存在逻辑漏洞,昨日突然失效返回用户最新订单时间,给ox.create_time加上Min()函数后可得到正确的首单时间。
Select o.id as 'order_id', u.id as 'user_id', Min(x.create_time) as 'user_1st_order_date' -- 外层需聚合确保取首单时间 from tb_order o left join tb_user u on o.user_id = u.id /* 此处约有10个关联查询 */ left join ( select ux.id, Min(ox.create_time) as create_time -- 子查询内必须用Min()获取用户最早订单时间 from tb_user ux left join tb_order ox on ox.user_id = ux.id where ( ox.order_status = 1 or ox.order_status = 2 ) /* 订单状态为已完成或已取消 */ group by ux.id ) x on x.id = u.id where o.create_time != 0 and ( o.order_status = 1 or o.order_status = 2 ) group by o.id
原思路是通过
group by ux.id聚合用户订单,但未加聚合函数时,MySQL会随机返回一条匹配的create_time,导致结果错误。
Option 2: 子查询方案
Select o.id as 'order_id', u.id as 'user_id', ( select ox.create_time from tb_order ox where (ox.order_status = 1 or ox.order_status = 2) and ox.user_id = u.id order by ox.id asc limit 1 ) as 'user_1st_order_date' from tb_order o left join tb_user u on o.user_id = u.id /* 此处约有10个关联查询 */ where o.create_time != 0 and ( o.order_status = 1 or o.order_status = 2 ) group by o.id
性能对比与最优选择
优化后的Option 1:
- 子查询先批量预计算所有符合条件用户的首单时间,再与主查询关联,属于一次性批量处理模式。如果给
tb_order创建复合索引idx_user_status_create(user_id, order_status, create_time),子查询的聚合效率会大幅提升,主查询关联时也能快速匹配。 - 优势:仅需一次预计算,主查询关联成本低,适合数据量较大的每日更新场景。
- 子查询先批量预计算所有符合条件用户的首单时间,再与主查询关联,属于一次性批量处理模式。如果给
Option 2:
- 属于关联子查询,主查询每返回一行,就会触发一次子查询去获取对应用户的首单时间。如果主查询结果集行数较多(比如上万条以上),重复执行子查询的开销会非常高,性能急剧下降。
- 劣势:小数据集下表现尚可,但大数据量场景下性能拉胯,不适合每日更新的Tableau数据源。
结论:优先选用优化后的Option 1方案,同时配合上述复合索引进一步提升性能。
内容的提问来源于stack exchange,提问作者Data_Kevc
相关产品推荐
相关产品推荐

