云数据库技术

SQL编程大赛冠军-郭其:1.184 秒夺冠!通过 Oracle 完成百万车票分配的SQL 底层逻辑和算法技巧解读

Image

数据库编程大赛:只用一条 SQL 秒杀 100 万张火车票

2024 第二届数据库编程大赛于 12 月 5 日正式开启初赛!由 NineData 和云数据库技术社区主办,华为云、Doris等协办单位和媒体共同举办。比赛要求选手设计一套SQL算法,只用一条 SQL 秒杀 100 万张火车票,让乘客都都能顺利坐上火车回家过年。查看赛题详情

以下是本次决赛冠军选手,郭其的参赛介绍:

Image
参赛选手:郭其
选手简介:中国移动设计院网研中心,数据库技术专家负责人  
参赛数据库:Oracle
性能评测:百万级数据代码性能评测 1.184 秒
综合得分:96
以下是郭其选手的代码说明思路简介:

第一诫:所有影响hash join性能的优化,在极致性能要求面前,都是负反馈。

1. tmp_train临时通过开窗函数,将坐票累加值、站票累加值都记录下来,为后面的记录乘客匹配做准备,人为创造票数区间。

2. 充分审题,还要要多审题,发现票数只有600,800,1200,1600四种,站票固定10%(60,80,120,160),则取公约数20为最合适的值,进行匹配,便于匹配的时候减少hash小表的数据量,进一步提升hash性能。

3. tmp_train_seed,2中已说明,只有四种可能,因此借train表当个序列(按最大1600+160,1-88)生成使用下,提升代码美观度,把train表按公约数20扩行。

4. passenger_rk表是passenger表的开窗所得,开了两个列,一个是纯顺序rk,便于后期算车厢后使用。一个是按公约数20做的分组,20个一组,正好可以匹配一个批次。

5. 按人员批次和火车批次,进行left join匹配,因全是等值条件,hash 效率较高,注意这里,不能贪一些过滤条件,比如使用or+between去判断,会得到负反馈性能。

6. hint parallel(8)充分利用oracle的并行特性叠加hash join,提升性能。匹配处理,行数正确,剩下的只是一些基本面字段通过数字求车厢号,以及substr处理座位号的问题了。

    • 卷数据库:适当修改细节,将SQL放入doris中运行,观察执行情况,测试环境中,取速度更快的一个。

    • 优化点:hash join+按20批次分组拆分。

以下是郭其选手的详细算法说明,结尾附完整SQL:

Image

Image
Image
Image
Image
Image
Image
Image
Image
Image

参赛完整SQL:
with tmp_train as (    select            t.*,           sum(seat_count) over(partition by departure_station,arrival_station order by train_id)-seat_count seat_count_ac,          sum(seat_count*0.1) over(partition by departure_station,arrival_station order by train_id)-seat_count*0.1 no_seat_count_ac,          sum(seat_count) over(partition by departure_station,arrival_station) seat_total     from train t),tmp_train_seed as(    select t1.*,t2.n,            case when n*20>seat_count then seat_total+no_seat_count_ac-seat_count                else seat_count_ac end seat_tmp    from tmp_train t1,(select rownum n from train where rownum<=88) t2      where t1.seat_count*1.1>=t2.n*20) ,passenger_rk as (              select           passenger_id,          departure_station,          arrival_station,          row_number() over(partition by departure_station, arrival_station order by null) rk ,--直接顺序号          floor((row_number() over(partition by departure_station, arrival_station order by null)-1)/20)+1 rn --按20一个批次的批次号    from passenger t )select /*+parallel(8)*/ t1.passenger_id,t1.departure_station,t1.arrival_station,t2.train_id,      case when t1.rk<=seat_total then floor((t1.rk-seat_count_ac-1)/100)+1            else null end coach_number,      case when t2.train_id is null then null           when t1.rk<=seat_total then mod(floor((t1.rk-seat_count_ac-1)/5),20)+1 ||                                       substr('ABCEF',mod(t1.rk-seat_count_ac-1,5)+1,1)            else '无座' end seat_number from  passenger_rk t1 left join tmp_train_seed t2 on t1.rn=t2.n+(t2.seat_tmp/20) and t1.arrival_station=t2.arrival_station and t1.departure_station=t2.departure_stationorder by t1.passenger_id
感谢大家对本次《数据库编程大赛》的关注和支持,欢迎加入技术交流群,更多精彩活动不断,欢迎各路数据库爱好者来挑战!

Image