SQL编程大师程宁:突破性能瓶颈!Oracle 性能 2.198 秒背后的设计思路
数据库编程大赛:只用一条 SQL 秒杀 100 万张火车票
2024 第二届数据库编程大赛于 12 月 5 日正式开启初赛!由 NineData 和云数据库技术社区主办,华为云、Doris等协办单位和媒体共同举办。比赛要求选手设计一套SQL算法,只用一条 SQL 秒杀 100 万张火车票,让乘客都都能顺利坐上火车回家过年。查看赛题详情
以下是本次决赛第6名,大赛三等奖获得者程宁的参赛介绍:
以下是程宁选手的代码说明思路简介:
1. 每个乘客按行程规划分组生成需求序列号;
2. 所有火车按行程规划生成每个座位的供应序列号,先分配有座、再分配无座
3. 行程相同两个序列号相同,生成车票分配方案
增加一个行程方案,降低座位序列号计算量;用行程方案ID关联,降低最后JOIN关联字段消耗; 对火车进行预测,尽量少生成火车坐席; 添加了一个测试参数,可以将原始数据扩大N倍,用于性能测试; 加Hint /*+ PARALLEL(8) */ ,官方测试环境4C8G,网上查了一下,据说ORACLE默认安装的时候每核2个线程,故参数设置为8,榨干服务器。
参赛完整SQL:
with/*参数表:1600个数字*/seat_id(n) AS (select ROW_NUMBER() OVER(order by passenger_ID ) nfrom passenger WHERE rownum<=1600),/*测试参数,在有限数据情况下,扩大测试倍数至第二阶段*/test_train(n) as(select ROW_NUMBER() OVER(order by passenger_ID ) nfrom passenger WHERE rownum<=1),test_passenger(n) as(select ROW_NUMBER() OVER(order by passenger_ID ) nfrom passenger WHERE rownum<=1),/*计划表,用于减少检索数据量*/PlanS as (SELECT ROW_NUMBER() OVER(order by departure_station,arrival_station) Plan_id,departure_station,arrival_station,n_maxFROM (SELECTdeparture_station,arrival_station,count(1) n_maxFROM passenger a ,test_passenger bGROUP BY departure_station,arrival_station ) aa ),/*需求表:为每个乘客生成一个需求号*/needs as (select a.passenger_id,c.Plan_id ,a.departure_station,a.arrival_station,ROW_NUMBER() OVER(PARTITION BY Plan_id ORDER BY Plan_id,passenger_id) AS row_numfrom passenger a,test_passenger b,PlanS cwhere a.departure_station=c.departure_station and a.arrival_station=c.arrival_station),/*以下代码用于预测需要哪些火车生成座位信息,降低最后join计算关联的数据量*/v_train0 AS(SELECT a.train_id,c.Plan_id,a.seat_count ,b.n,c.n_max,1 pxfrom train a,test_train b,PlanS cwhere a.departure_station=c.departure_station and a.arrival_station=c.arrival_stationUNION ALLSELECT a.train_id,c.Plan_id,a.seat_count ,b.n,c.n_max,2 pxfrom train a,test_train b,PlanS cwhere a.departure_station=c.departure_station and a.arrival_station=c.arrival_station),v_train1 as(select a.train_id,a.Plan_id,a.seat_count ,a.n,a.n_max,a.px,SUM(CASE a.px WHEN 1 THEN a.seat_count ELSE a.seat_count * 0.1 END ) OVER (PARTITION BY a.Plan_id order by a.px,a.seat_count desc) seat_count_allfrom v_train0 a),v_train_min AS(SELECT plan_id,min(seat_count_all) seat_count_all_minFROM v_train1WHERE seat_count_all >= n_maxGROUP BY plan_id),v_train AS(SELECT a.train_id,a.plan_id,a.px,a.seat_countFROM v_train1 a WHERE a.seat_count_all < n_maxUNION allSELECT a.train_id,a.plan_id,a.px,a.seat_countFROM v_train1 a,v_train_min bWHERE a.plan_id = b.plan_id AND a.seat_count_all <= b.seat_count_all_min AND a.seat_count_all>=n_max),/*座位表:按有座、无座分别生成所有坐席*/allzuowei as(select a.train_id,a.plan_id,px,b.n seat_idfrom v_train a,seat_id bwhere a.px = 1 AND b.n<=a.seat_countunion allselect a.train_id,a.plan_id,px,b.n seat_idfrom v_train a,seat_id bwhere a.px = 2 AND b.n<=a.seat_count/10),/*供应表:按题目要求分配一个流水号*/stocks as(select a.train_id,a.plan_id,a.px,a.seat_id,ROW_NUMBER() OVER(PARTITION BY plan_id ORDER BY plan_id,px) AS row_numfrom allzuowei a)/*供需关联得到答案,*/select /*+ PARALLEL(8) */a.passenger_id,a.departure_station,a.arrival_station,b.train_id,case b.px when 1 then CEIL(b.seat_id/100) end coach_number ,case b.px when 1 thenFLOOR((b.seat_id- FLOOR(b.seat_id/100) * 100) /5) + 1 ||case MOD(b.seat_id,5)when 0 then 'F' when 1 then 'A' when 2 then 'B' when 3 then 'C' when 4 then 'E' endwhen 2 then '无座' end seat_numberfrom needs aleft OUTER join stocks b on a.plan_id=b.plan_id and a.row_num=b.row_numorder by a.passenger_id