
简介面向2025全国大学生计算机系统能力大赛第五届OceanBase数据库大赛参赛者的完整代码资源包涵盖数据库核心工程源码、构建配置与竞赛相关文档适合正在备赛的高校学生及想深入理解OceanBase源码体系的开发者使用。压缩包共含2000个文件体积约114MB以C/C头文件与源码为主辅以Python脚本、Markdown文档、JSON配置及cmake/bazel构建文件另含Git历史对象文件便于追踪源码演进与版本变更。包内提供了sample与expected等样例数据可配合源码复现大赛相关实验场景各模块的目录结构完整结合源码注释与文档有助于系统梳理OceanBase的核心实现、存储引擎与调优思路为参赛调试和方案设计提供参考。已有50人学习下载为备赛阶段的代码研读、环境搭建与实验对照提供了较完整的基础材料。1. 一场数据库大赛为什么值得从装库到调优完整走一遍第一次看到 2025 全国大学生计算机系统能力大赛里的 OceanBase 数据库大赛时我以为是又一场「给个环境、调调参数、交个报告」的学科竞赛。真进去之后才发现赛题给的是一套真实的分布式数据库内核你要自己部署集群、跑通压测、读懂执行计划再在海量参数里找出哪一个是性能瓶颈。第五届的考察范围比前几届更广初赛阶段就把部署、SQL 调优、事务并发、资源隔离全串在一起决赛直接落在源码级修改和特性验证上。我写这篇笔记的受众很明确准备参赛的本科生、刚读研想走数据库方向的硕士以及那些在工作中听说过 OceanBase 但一直没机会系统性上手的工程师。这篇文章不教你背概念而是给你一条能照做的路径——从架构主线开始把最小集群跑起来用压测建立基线再一步步逼近调优和避坑。你会看到每个命令我都给了注释每个参数我都交代了为什么改、改完会有什么副作用。哪怕你最后不参赛这套「部署—压测—调优—回归」的方法也足够你迁移到任何分布式数据库项目上。2. 找对发力点先看懂 OceanBase 的架构再决定优化什么任何比赛都一样规则决定策略。第五届 OceanBase 数据库大赛的赛制公开信息很明确初赛以线上排位和压测报告为主考察部署、SQL 优化、系统调优决赛则以开发任务和现场答辩为主需要选手在源码层面动手。这意味着如果你只会「启动集群、跑个压测」初赛能过决赛必挂。想在决赛里活下来你必须把架构理解前置。2.1 大赛考察的四层能力部署、SQL、并发、源码我把历届赛题公开材料的考察方向拆成四个能力层对应关系大致是这样第一层是部署运维能力。给你一台或多台服务器你得把 OceanBase 集群装起来租户建好账号权限配对压测工具跑通。这一层是入场券卡住的人从来不在少数。第二层是 SQL 优化能力。赛题会给一批压测模型比如点查、范围查、读写的混合负载。你要能看懂执行计划知道为什么这个 SQL 没走索引、那个 JOIN 为什么选了 Hash Join、分区裁剪为什么没生效。第三层是并发与事务调优能力。压测场景一旦上升到高并发锁等待、死锁重试、事务超时都会成为瓶颈。这一层考察你对并发控制的直觉不能只会加索引。第四层是源码阅读和特性开发能力。决赛任务往往围绕 OceanBase 的某个模块展开比如优化某个算子、改进转储策略、修复一个并发 bug。没有前三层的积累这一层根本无从下手。所以备赛顺序应该是先学架构原理再练部署再刷压测调优最后啃源码。反过来做的人多半在前三层就耗尽时间了。2.2 两条主线LSM-Tree 与日志流OceanBase 和 MySQL 最大的区别不在 SQL 语法而在存储引擎和复制机制。你把这两条主线吃透后面调参时就知道自己在调什么。第一条主线是 LSM-Tree。传统 B 树在更新数据时要原地修改页随机写会打穿磁盘的 IOPS。OceanBase 把写操作先落到内存里的 MemTable数据积累到阈值后冻结成 SSTable 文件后台再做合并。这套设计让写入变成顺序追加吞吐自然高。代价是读路径变复杂一个 Key 可能同时存在于 MemTable 和多个 SSTable 里查询时要逐层找这就是经典的读放大。这个「基础知识概念」的进阶版本决定了你在调优时为什么总盯着内存和合并参数。第二条主线是日志流。OceanBase 把一个租户的数据按分区打散到多台 observer 上每个分区组对应一个日志流日志流通过 Paxos 协议在副本间同步。数据写入时先记 clogRedo 日志同步到多数副本才返回成功。这套机制下单点故障不会丢数据但也带来了网络往返和日志落盘的额外开销。这两条主线是观察问题的坐标系压测慢先判断是存储引擎的合并干扰还是日志流同步的延迟。判断错了后面调什么都白调。2.3 用最小实验验证你的架构理解租户与副本理解架构最有效的方式不是多读两遍文档而是把集群拉起来看内部视图。初赛环境里我最常做的一组 SQL 是下面这样的-- 查看当前集群里的租户信息 SELECT tenant_id, tenant_name, compatibility_mode, status FROM DBA_OB_TENANTS; -- 查看各 observer 节点的资源水位 SELECT svr_ip, svr_port, zone, cpu_capacity, memory_capacity FROM GV$OB_SERVERS; -- 查看资源单元的分布确认租户的副本落在哪台机器上 SELECT tenant_id, svr_ip, zone, unit_id, max_cpu, memory_size FROM GV$OB_UNITS;第一段 SQL 用来确认租户创建成功compatibility_mode会告诉你这个租户是 MySQL 模式还是 Oracle 模式压测账号的建法会因此不同。第二段 SQL 帮你建立「集群里到底有几台可用机器」的直觉——很多新手以为一台机器就是集群实际上 OceanBase 的集群概念是逻辑的单机上也可以起多副本但你要清楚每个 observer 进程承担了多少资源。第三段 SQL 最有价值它会直接告诉你租户的数据副本落在哪个 Zone、哪台机器上负载均衡有没有生效一目了然。做完这三个查询你就把「租户 资源池 资源单元」这套抽象落到了实处。后面创建租户、配置副本时脑子里有画面不会凭感觉写参数。3. 从零部署 OceanBase用 obd 把最小集群拉到能压测部署是初赛的第一道坎。网上能搜到不少 OceanBase 数据库安装教程但大多是面向生产环境的图形化部署。比赛场景更讲究「最小可用 可控」所以我推荐你直接用 obd 命令行工具它是一条命令能解决的问题里最不容易出错的那条路。3.1 资源规划2C4G 能跑但你要知道瓶颈在哪很多参赛队到手的机器配置并不高甚至只有 2C4G。这个配置能跑 OceanBase 吗能但必须做取舍。我的建议是内存至少 4G其中给 observer 进程分 3G 左右剩余留给操作系统和压测工具磁盘建议 50G 以上因为 OceanBase 的 clog 和数据文件会持续增长压测时间越长日志空间占用越大CPU 只有 2 核的话并发线程数就别顶到 64压测线程超过 CPU 核数太多只会放大调度开销。资源规划的原则是「宁可让数据文件小一点也要给内存留足空间」。内存不够最直接的后果是转储频繁性能一落千丈这是我在学生团队里见过最多的翻车原因。3.2 用 obd 部署yaml 配置和两条核心命令obd 的部署思路是「一个 yaml 文件描述整个集群一条命令拉起所有组件」。我先给你一份能跑通的最小配置# oceanbase-ce.yaml oceanbase-ce: servers: - name: server1 ip: 192.168.1.10 global: home_path: /home/oceanbase data_dir: /data/oceanbase log_dir: /log/oceanbase memory_limit: 8G datafile_size: 24G log_disk_size: 30G system_memory: 2G devname: eth0这里几个参数的含义分别是memory_limit是整个 observer 进程能使用的内存上限比赛机内存不够时优先调它datafile_size是数据文件的大小相当于「给 SSTable 预留的磁盘空间」log_disk_size是 clog 日志盘的容量写事务频繁时必须给足system_memory是租户系统线程跑内部任务的内存太小会导致后台任务饥饿。配置文件准备好后执行两条命令# 部署集群-c 指定配置文件 obd cluster deploy obtest -c oceanbase-ce.yaml # 启动集群 obd cluster start obtest # 查看集群状态和组件端口 obd cluster display obtest部署命令会检查软件包和系统依赖第一次跑会花几分钟下载组件。start命令拉起 observer 进程display则输出集群的关键信息包括内部 IP 和端口。启动后立刻用obd cluster list看一眼有没有组件没起来再进入下一步。3.3 初始化租户和账号压测前必做的三步集群起来后默认只有一个sys租户它是管理租户不能直接拿来跑压测。你需要为业务单独建一个租户标准动作是三步-- 第一步创建资源单元定义 CPU 和内存规格 CREATE RESOURCE UNIT u_test MAX_CPU 2, MEMORY_SIZE 4G; -- 第二步创建资源池把资源单元归属到池里 CREATE RESOURCE POOL p_test UNIT u_test, UNIT_NUM 1; -- 第三步创建业务租户绑定资源池 CREATE TENANT test RESOURCE_POOL_LIST (p_test);资源单元是规格资源池是容器租户是实际使用者。UNIT_NUM 代表这个资源池下有几个单元单机环境填 1 就够了填多了反而会分配失败。租户创建成功后还需要在里面建一个具备压测权限的账号-- 需要在 test 租户下执行注意租户名的前缀方式 CREATE USER bench% IDENTIFIED BY bench_password; GRANT ALL PRIVILEGES ON *.* TO bench%;这一步很多人会漏掉CREATE USER必须在业务租户里执行而不是在 sys 租户里。连错租户是初赛最常见的问题。3.4 别急着调参先用 sysbench 拿到基线集群和租户都就绪后第一件事不是调参数而是跑一遍压测拿到性能基线。我通常用 sysbench 做读和读写混合两轮测试# 准备数据生成 16 张表每张 10 万行 sysbench --db-drivermysql \ --mysql-host127.0.0.1 --mysql-port2883 \ --mysql-userbenchtest --mysql-passwordbench_password \ --mysql-dbtestdb \ oltp_common --tables16 --table-size100000 prepare # 执行读写混合压测记录 TPS 和 95 分位延迟 sysbench --db-drivermysql \ --mysql-host127.0.0.1 --mysql-port2883 \ --mysql-userbenchtest --mysql-passwordbench_password \ --mysql-dbtestdb \ --threads32 --time300 --report-interval10 \ oltp_read_write run默认端口 2883 是 obproxy 的入口如果你没装 obproxy要改成 observer 的 2881 端口。连接串里benchtest是一种特殊的表达方式前半段是用户后半段是租户名。跑完这一轮把 TPS、QPS、平均延迟、95 分位延迟记下来。这套数值就是你后续所有调整的参照系没有基线就谈不上优化这是我最开始备赛时被反复强调的铁律。4. 性能调优的主战场执行计划、并发等待与合并参数部署只是开胃菜初赛真正拉开差距的部分是性能调优。OceanBase 的调优不是盲目改参数碰运气而是按「执行计划 → 并发冲突 → 存储引擎状态」这个顺序排查。每个环节都有对应的观测手段下面按我实际的排查顺序展开。4.1 让执行计划开口说话explain 里藏着 80% 的问题SQL 慢第一件事永远是用EXPLAIN看执行计划。OceanBase 的 MySQL 模式支持两种写法我建议用带EXTENDED的版本它会额外输出谓词下推、表达式计算这些细节EXPLAIN EXTENDED SELECT * FROM orders WHERE o_custkey 100 AND o_orderdate 2024-01-01 AND o_orderdate 2024-02-01;执行计划里你只需要盯几个关键点第一个是TABLE SCAN还是INDEX SCAN如果出现TABLE SCAN加上filter条件里的字段和索引对不上说明在走全表扫描第二个是access部分这里能看到实际命中的索引名第三个是 JOIN 的顺序和算法NESTED-LOOP JOIN和HASH JOIN的选择会直接影响大数据量下的查询耗时。看完计划再动手优化。上面这条 SQL 如果orders表已经有主键o_custkey但o_orderdate没有索引范围过滤就要逐行判断这时加一个联合索引把两个条件串起来CREATE INDEX idx_cust_date ON orders(o_custkey, o_orderdate);加索引是最廉价的优化但它不是银弹。索引太多会拖慢写入压测模型是写多读少时索引的价值就要重新评估。执行计划是你手里的地图没有地图就调参跟闭着眼睛开车没区别。4.2 并发锁与死锁怎么分辨「慢查询」和「锁等待」压测到高并发阶段最容易遇到的困惑是TPS 突然掉到原来的三分之一但看 CPU 和 IO 都正常。这时候蹲在 SQL 层面看毫无意义问题一般出在并发控制上。OceanBase 的行锁机制和传统数据库类似事务更新同一行时后到的事务要等前一个事务提交或回滚。高并发下如果压测模型里有热点行锁等待会无限放大。Sysbench 的 oltp_read_write 模型里order_line表往往是热点因为它被所有线程集中更新。排查锁问题的路径是先看活跃事务视图再确认锁等待是否集中在少数几张表上-- 查看当前正在运行的事务及其状态 SELECT svr_ip, trans_id, tenant_id, status, elapsed_time FROM GV$OB_TRANSACTION_PARTICIPANTS WHERE state ACTIVE AND tenant_id 1002; -- 查看锁等相关的等待事件 SELECT /* PARALLEL(8) */ sql_id, event, wait_class, wait_time_micro FROM GV$OB_SQL_AUDIT WHERE tenant_id 1002 AND elapsed_time 100000 ORDER BY wait_time_micro DESC LIMIT 20;锁问题对应的等待事件通常会显示为行锁等待或者事务提交等待。这一类问题的解法不是加索引而是缩小事务体把一次事务里的操作条数降低、把批量提交改为小批量提交、把无关的二级索引在压测前暂时拆掉减少写放大。死锁则是锁等待的极端情况OceanBase 有死锁检测机制但检测周期是按秒计的遇上死锁时你会看到连接直接报错并回滚。压测场景里死锁频繁出现基本可以判定是热点行分布太集中业务模型需要调整。4.3 内存、冻结与合并OceanBase 特有的三个关键参数架构那章提到的 LSM-Tree 主线在这里要派上用场了。OceanBase 的写入是先写内存 MemTable再逐步转储、合并到磁盘。这个生命周期里有三个参数直接决定写入稳定性和查询延迟参数作用调小的影响调大的影响freeze_trigger_percentage触发 MemTable 冻结转储的内存水位更早转储写入稳定但转储频繁更晚转储内存利用率高但容易顶到上限memstore_limit_percentage租户 MemStore 占租户内存的上限留给转储的缓冲小高并发易报内存不足能扛更高写入峰值但挤压读缓存major_compact_trigger触发全量合并的 SSTable 数量阈值合并更早读放大控制好但合并且开销大合并更少写放大低但读路径更长具体调优动作是这样落地的-- 查看当前参数值 SELECT name, value, scope FROM GV$OB_PARAMETERS WHERE name IN (freeze_trigger_percentage, memstore_limit_percentage, major_compact_trigger); -- 调整转储触发水位把阈值从默认值调小让转储更早发生 ALTER SYSTEM SET freeze_trigger_percentage 50; -- 调整合并触发阈值减少高频全量合并 ALTER SYSTEM SET major_compact_trigger 50;调整的节奏有讲究如果压测时出现偶发的大延迟尖刺优先调小freeze_trigger_percentage让转储均匀发生如果压测冲到一半就报内存不足则要调小各租户的内存预留给memstore_limit_percentage腾空间。改完参数不是立刻有效的要观察转储事件-- 查看合并和转储是否在正常推进 SELECT svr_ip, version, status, start_time, end_time FROM GV$OB_MAJOR_COMPACTION;这组参数是整个比赛里最容易出现「玄学调参」的地方。你没看错很多队伍就是拿一堆参数轮着改跑一轮压测看数字变化毫无分析逻辑。我自己的做法是一次只改一个参数跑完一轮完整的压测记录数据再改下一个。4.4 把改动记录成回归用例比反复调参更重要这一小节不是讲某个 SQL而是讲一种习惯。性能调优最怕的不是没效果而是你根本不知道哪个改动产生了效果。我的做法是准备一张回归记录表每一行记录一次改动参数名、改动值、改动前 TPS、改动后 TPS、压测时间、并发线程数、是否强制合并过。这张表的作用在比赛答辩时尤其明显评委问你「你做了哪些优化、效果如何」时你递出的是一张有数据、有时间、有复现步骤的表格而不是一段「我调了内存参数然后变快了」的模糊描述。同时每个压测场景都固定成一个脚本用同一套参数跑。没有固定场景和固定流程两轮压测之间的数据差异可能来自缓存状态、后台合并而不是你的改动。5. 备赛避坑OceanBase 调试里五个最容易翻车的场景这一章我从备赛和带队的经验里挑了五个高频问题。这些问题不是来自某一次特定比赛而是分布式数据库调试中反复出现的典型场景。每条都按「现象 → 原因 → 解决」的顺序给你方便你直接对照排查。5.1 集群起来了obclient 却连不上现象obd 显示集群状态是 RUNNINGobclient 连接时却报 timeout 或 Access denied。原因最常见的是租户还没建或者连到了 sys 租户而不是业务租户。其次是防火墙把 2881/2883 端口挡了obproxy 也没启动。还有一个隐蔽原因连接串里的租户名写错rootsys#obcluster和benchtest的格式完全不同。解决先执行obd cluster list看每个组件的状态确认 obproxy 活着再用netstat -ltnp | grep 2881确认端口在监听最后检查连接串确认-u参数里带的是用户租户的格式。端口不通时到安全组和防火墙里放行对应端口。5.2 压测一跑日志里出现「No memory or reach tenant memory limit」现象sysbench 跑了 5 分钟客户端开始大批量报错报错信息里带No memory or reach tenant memory limit。原因写入速度超过了转储释放内存的速度MemStore 被写满新写入的事务无法申请内存。通常是你并发线程数开得太大或者freeze_trigger_percentage设得过大转储死活不触发。解决压测前先观察GV$OB_MEMSTORE的内存水位确认转储线程在工作把freeze_trigger_percentage调到 50 以下让转储更早发生同时把 sysbench 并发从 64 降到 16观察两分钟确认内存水位稳定后再逐步加大并发。记住一个判断标准内存水位曲线应该是锯齿状而不是一根直线冲到顶。5.3 改完参数重启集群再也起不来现象你按照网上的建议把memory_limit调大了一倍重启后 obd 报进程拉起失败observer 日志里反复出现内存分配失败。原因参数改得不合理。机器物理内存只有 8G你把memory_limit调到 12Gobserver 进程在启动阶段就要申请大量内存直接被系统 OOM 杀死。我见过不止一支队伍因为这个卡了整整一个下午。解决参数改之前先存档obd cluster edit-config obtest会把当前配置先打一个备份这个备份就是你最后的后悔药。如果已经改挂了用obd cluster edit-config把参数改回原值或者执行obd cluster destroy obtest重新部署。更稳妥的做法是每次只改一个参数改完先查GV$OB_PARAMETERS确认生效再重启不要一次性堆多个改动。5.4 同一套压测两轮结果相差 30%现象压测脚本完全一样第一轮 TPS 是 5000第二轮直接掉到 3500第三轮又回到 4800。原因后台合并事件在捣乱。第一次压测时集群刚启动SSTable 数量少读路径短跑了几分钟后触发了转储或合并查询要跨层读多个 SSTable延迟就上去了。这种情况下的性能波动不是你的改动造成的是存储引擎的固有行为。解决压测前手动触发一次合并把系统稳定到一个「干净」状态再开始采集数据ALTER SYSTEM MAJOR FREEZE;执行后通过GV$OB_MAJOR_COMPACTION确认合并完成然后再跑压测。正式记录数据时同一场景至少跑两轮取平均值不要用单轮数据下结论。5.5 比赛机器只有 2C4G地球人都知道资源不够现象限定配置的低配机器上observer 能启动但 sysbench 的oltp_read_only都压不出像样的 QPS日志里全是转储记录。原因4G 内存里observer 吃掉 3G剩下的 1G 给系统和其他进程MemTable 的可用空间非常小。压测一开写入很快就把内存写穿转储被反复触发每一轮转储都要占用 CPU 去写磁盘性能自然彻底崩掉。解决低配环境下要把预期拉低内存参数按实际容量设置memory_limit给 3Gsystem_memory给 1G数据文件不要贪大datafile_size20G 足够跑完整个初赛并发线程数控制在 8 到 16用 point_select 这种轻量模型验证系统正常再用 read_write 模型去冲极限。低配机考验的不是你的调优技巧而是你对资源上限的理解做减法也是一种能力。6. 让优化经得起复现把一次调优沉淀成一套验证动作比赛的最后一个阶段写报告和答辩时我最深的体感是调优过程如果不沉淀成脚本哪怕你把集群调得再快也很难在有限的时间里向评委证明你的工作。所以我的习惯是把每一个优化动作固化成可重复的验证流让它变成一套任何时候都能还原的现场。我定义了两个很小的脚本。第一个是压测前的环境复位脚本作用是让每一轮压测都从相同的冷缓存状态出发#!/bin/bash # bench_once.sh # 用法./bench_once.sh read_write MODE$1 # 环境复位重启集群清空缓存强制合并 obd cluster stop obtest obd cluster start obtest sleep 20 # 手动触发合并等合并状态变为 IDLE obd cluster exec obtest ALTER SYSTEM MAJOR FREEZE sleep 60 # 执行指定模型的压测时间固定为 300 秒 sysbench --db-drivermysql \ --mysql-host127.0.0.1 --mysql-port2883 \ --mysql-userbenchtest --mysql-passwordbench_password \ --mysql-dbtestdb --threads$2 --time300 --report-interval5 \ oltp_${MODE} run第二个是参数快照脚本每次实验前把集群配置和内部参数导出来作为答辩时的证据。评审问起改动历史时你展示的是一份完整的参数快照而不是记忆里的碎片。# 导出当前集群配置和内核参数按时间戳归档 TIMESTAMP$(date %Y%m%d_%H%M) obd cluster dump obtest config_${TIMESTAMP}.yaml obd cluster exec obtest SELECT name, value FROM GV\$OB_PARAMETERS params_${TIMESTAMP}.csv这套动作看似简单真正坚持做的人不多。我自己第一年参赛就是在「调完参数不知道哪个有效」的混乱状态里结束的后来养成了「每次实验前先写脚本、跑完立刻归档」的习惯第二年备赛的所有结论都能在十分钟内复现。赛后复盘时我不再依赖记忆而是依赖这套脚本和归档表。如果你现在刚开始备赛我建议你从今天起就建立这张基线表每次改动一个参数、每次跑完一轮压测都把结果填进去。它不会让你的集群立刻变快但它会让你的每一次努力都变成可追溯的资产也会让评委相信你真的理解自己做的事情。希望帮到你。本文还有配套的精品资源点击获取