2024 数据库编程大赛 - 八强决赛成绩揭晓
2024 第二届数据库编程大赛于 12 月 5 日正式开启初赛!由 NineData 和云数据库技术社区主办,华为云、Doris 等协办单位和媒体共同举办。
赛题回顾
比赛要求选手设计一套 SQL 算法,只用一条 SQL 秒杀 100 万张火车票,让乘客都都能顺利坐上火车回家过年。查看赛题详情。在将近两周的赛程报名中,共有数百位数据库爱好者积极参与。选手们展现了极高的参与热情和竞争强度,每天高频次地提交评测,力求在性能上不断突破。
评委团和评选规则
本次大赛的评委,均是数据库领域的领军人物,通过主办方 NineData 的邀请,组成 2024 年《数据库编程大赛》强大的评审嘉宾团。
《2024 数据库编程大赛》评审嘉宾团
本次大赛主要以下 4 个方面评审:
正确性
由组委会验证,只有结果正确才会进入评委评选流程。
性能
总得分占比 50% ,由组委会给出测试成绩(第一名 50 分,第二名 45 分,第三名 42 分,第四名 40 分,第 5 名 39 分,后面每排名下降一名减少 1 分,以此类推,最低得 20 分)
代码创新性
总得分占比 30% ,由评委打分( 0~30 分)
代码易读性
总得分占比 20% ,由评委打分( 0~20 分)
决赛答辩
前 8 强 SQL 编程大师诞生
经过大赛组委会对参赛数百份 SQL 代码的初步验证和确认,确保其正确性。在接下来的百万级别性能评测中,我们选出了最强的 8 位选手,成功晋级到决赛答辩环节。
本次大赛由 8 位权威评委组成的专家团队对选手的 SQL 参赛代码和答辩解读进行评分。评委们将着重考察代码的创新性和易读性,并将分别对这两个方面进行打分。最终,大赛组委会将根据综合得分,确定选手的最终排名。
以下是本次数据库编程大赛的 8 位选手简介和 SQL 说明概要:
第 8 名:李鹏军
参赛选手:李鹏军
个人简介:就职于 QuestMobile 公司,从事数据开发工作 9 年。
参赛数据库:SQL Server
性能评测:百万级数据代码性能评测 5.230 秒
综合得分:71
以下是李鹏军的代码说明思路:
1. 车厢分配:通过(乘客编号 - 车次起始乘客编号)÷ 100(车厢座位数)并向上取整来确定。
2. 座位分配:
排数:总体是乘客编号最后两位 ÷5(每排座位数)并向上取整,针对乘客编号为 100 整数倍时结果异常的情况,通过小技巧转换处理。
列数:即具体座位(A,B,C,E,F),通过(乘客编号 - 1)÷5 的余数 + 1 确定,针对乘客编号为 5 整数倍时结果异常的情况,也通过小技巧转换处理。
第 7 名:张永志
参赛选手:张永志
个人简介:十多年 Oracle 经验,6 年 MySQL 经验,擅长数据库性能调优、SQL 性能优化与索引设计。
参赛数据库:Oracle
性能评测:百万级数据代码性能评测 4.059 秒
综合得分:74.5
以下是张永志选手的代码说明思路简介:
1.首先生成列车的车票信息。借用自增数列 t_sequence,通过与 train 连接,生成车厢号、座位号。另外,生成 10%的无座车票。为便于对车票排序,额外生成是否有座说明列。
2.以出发站和到达站点对高铁车次分组,并按照是否有座对可售车票进行顺序编号,有座车票编号在前,无座车票编号在后。
3.乘客按照出发站和到达站点进行顺序编号。
4.车票和乘客顺序编号之后,根据出发站、到达站点、顺序编号对可售座票与乘客进行外连接,即完成车票分配。
性能优化考虑:
由于是全量数据连接操作,表字段个数少,且表连接操作都是在中间结果集之后进行的,创建索引没有多大意义,故无需额外创建索引。
若机器性能较好,可考虑添加并行 hint,使用并行查询技术。
可在排序内存和 hash 连接内存分配上多给一些,消除在磁盘上的排序。
第 6 名:程宁
参赛选手:程宁
个人简介:嘉兴市第二医院信息科,从事信息化工作多年
参赛数据库:ORACLE
性能评测:百万级数据代码性能评测 2.198 秒
综合得分:78.25
以下是程宁选手的代码说明思路简介:
1. 每个乘客按行程规划分组生成需求序列号;
2. 所有火车按行程规划生成每个座位的供应序列号,先分配有座、再分配无座
3. 行程相同两个序列号相同,生成车票分配方案
优化措施:
增加一个行程方案,降低座位序列号计算量;用行程方案 ID 关联,降低最后 JOIN 关联字段消耗;
对火车进行预测,尽量少生成火车坐席;
添加了一个测试参数,可以将原始数据扩大 N 倍,用于性能测试;
加 Hint /*+ PARALLEL(8) */ ,官方测试环境 4C8G,网上查了一下,据说 ORACLE 默认安装的时候每核 2 个线程,故参数设置为 8,榨干服务器。
第 5 名:傅昌林
参赛选手:傅昌林
个人简介:HBI Solutions, Inc, VP Engineering
参赛数据库:SQLServer
性能评测:百万级数据代码性能评测 2.702 秒
综合得分:80.75
以下是傅昌林选手的代码说明思路简介:
1. 构建带座位类型的列车表:复制 train 表,新增 seat_type 列,0 代表有坐票,1 代表无坐票,无坐票数量为有坐票的 10%,新表命名为 train_with_emapy。
2. 计算运行总计座位数:在 train_with_emapy 表中,利用窗口求和函数,按 <车次, seat_type> 在 <出发站, 到达站> 组内生成运行总计座位数 running_seat_count。
3. 在乘客表中,通过窗口计数函数,为 <出发站, 到达站> 组内的乘客生成从 1 开始的编号 passenger_rid。
4. 根据每个乘客在<出发站, 到达站>组里的编号里和车次在<出发站, 到达站>组里的编号范围,求出具体的车次号, 车票类型,车箱号 coach_number 和乘客在这个车次的编号 ans_id, 命名为 result
5. 生成 0..100 对应的 seat_number 的查找表 to_seat_number
6. 原乘客表 LEFT JOIN result 和 LEFT JOIN to_seat_number 得出最终答案
第 4 名:周仟荣
参赛选手:周仟荣
选手简介:数据分析师,来自东风日产数据服务有限公司
参赛数据库:PostgreSQL
性能评测:百万级数据代码性能评测 2.994 秒
综合得分:83
以下是周仟荣选手的代码说明思路简介:
1. 该需求中主要影响 SQL 性能的为大表连接和排序,优先分配有票也是与顺序有关,结题中将主要围绕这两点展开:减少表连接行数,减少非必要的排序但要达到排序效果。
2. 选用 PostgreSQL 解题,PostgreSQL 数组包含顺序,将乘客和座位表分别生成两个起始站-终点站聚合 数组,按起始站-终点站关联后展开,即可达到从头对齐后进行批量分配,极大的减少了表连接行数,可显著提升性能。
步骤(对应第 N 个 CTE):
将乘客按起始站和终点站分组并聚合为数组
为每列车的座位编号生成车厢和座位信息:
座位信息按起始站-终点站聚合为数组:
合并乘客和座位信息:
返回最终的座位分配结果:
最终输出
第 3 名:卢涛
参赛选手:卢涛
选手简介:ITPUB Oracle 开发版版主
参赛数据库:Doris
性能评测:百万级数据代码性能评测 1.299 秒
综合得分:84.5
以下是卢涛选手的代码说明思路简介:
进阶版相比普通版,主要是对普通版的有票乘客增加了计算票车厢和座位,以及对车次定员额外的 10%增加站票。
1. 有座票车厢和座位的计算方法:
将相同(起点站,终点站)的车次分成一个虚拟组,组内各个车次首尾相连,从第 1 趟车到最后一趟编写座位的总序号,座位总序号减去当前车次之前的座位总数就是在当前车次的座位序号,把当前车次的座位序号减一,得到从 0~本车次座位总和减一的新序号,因为每个车厢固定 100 个座位,所以把上述新序号除以 100 的商取整再加一就是车厢号。
座位号类似,将新序号除以 5,商为 0 放在第一排,1 放在第二排,以此类推,余数为 0-2 编 ABC 号,3-4 编 EF 号。
2. 无座票的序号和车厢计算方法:
将相同(起点站,终点站)的虚拟组座位总数除以 10,就是无座票的总张数,序号从虚拟组座位总数加一
开始编,每达到一个车次的额定人数的十分之一,就换下一个车次。无座票的座位统一填写‘无座’,车厢号为空。
第 2 名:柳胜勋
参赛选手:柳胜勋
个人简介:PingCAP/TiDB 研发工程师
参赛数据库:Doris
性能评测:百万级数据代码性能评测 1.201 秒
综合得分:88.25
以下是柳胜勋选手的代码说明思路简介:
1. 因为有 10%的站票,而且必须买完所有坐票才能买站票,所以我们需要把座位票和无座票分开来卖。即把一列车变成成两列车卖,一列是有座的票,二列是 10%的无座票。
2. 先处理有座的车,根据路径分组,车号排序。计算出每列车的起始编号。按照 200 个座位一页拆分。比如 800 座的火车,拆分成 4 个页,也就是变成 4 条记录,以此类推。
3. 拆分后,使用窗口函数进行编号座位页号,根据路径分组,车号排序。因为每页的大小都是一样的。所以 路径+页号 是唯一的。
4. 同时,我们使用函数窗口,计算出同一条线路上的乘客编号。
5. 因为有页号,所以可以乘客编号可以对 200 取模,使用 JOIN 快速映射到记录。
6. 通过这次 JOIN 后,有座的票就分好了。同时可以根据编号减去列车的起始编号,获得列车上的座位序号,然后除法和取余计算出座位号。
7. 将上一步的结果筛选出未分配列车的乘客,和上面一样的方式,只是按照 20 一页来取模计算页号。因为无座,所以不需要算车厢号,座位号直接标记无座即可。
8. 将结果和之前有座乘客 UNION ALL 一下,然后做最终排序就是结果。
第 1 名:郭其
参赛选手:郭其
选手简介:中国移动设计院网研中心,数据库技术专家负责人
参赛数据库: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 批次分组拆分。
大赛奖品
本次数据库编程大赛的奖项安排:一等奖(1 人)、二等奖(2 人)、三等奖(3 人)、阳光普照纪念奖(50 人)。
一等奖(1 人):2024 华为最新款 WATCH D2 + 奖杯
二等奖(2 人):高级行李箱 1 个 + 奖杯
三等奖(3 人):华为蓝牙耳机 1 个 + 奖杯
阳光普照纪念奖(50 人):充电宝 1 个
《数据库编程大赛》下一次再聚!
本次《数据库编程大赛》前 50 名完成普通挑战并通过正确性验证的选手,也是会获得一份精美礼品,感谢大家对本次大赛的关注和支持,我们下次再相聚!
评论