ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

青岛OJ数据库直连方案设计与优化实践

青岛OJ数据库直连方案设计与优化实践

1. 青岛OJ数据库直连方案设计背景

青岛OJ(QingdaoU OnlineJudge)作为高校ACM竞赛训练平台,其核心业务逻辑高度依赖数据库操作。传统通过API层间接访问数据库的方式在管理后台、数据统计等场景下存在明显性能瓶颈。我们团队在v6版本中引入数据库直连方案,主要解决以下三类问题:

  1. 批量操作效率问题:竞赛结束后需要同时更新上千条提交记录状态,API逐条处理耗时长达3-5分钟
  2. 复杂查询需求:管理员需要执行多表联合查询生成训练效果分析报表
  3. 紧急数据修复:当出现异常数据时,需要直接操作数据库进行快速修正

重要提示:直连数据库属于高危操作,必须严格限制访问权限。我们采用IP白名单+动态凭证双重验证机制,每个连接会话最长存活时间设置为10分钟。

2. 数据库架构与连接配置详解

2.1 容器化部署结构

青岛OJ采用Docker Compose定义的服务拓扑如下:

version: '3.8' services: oj-db: image: mysql:8.0 volumes: - ./mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PWD} MYSQL_DATABASE: qduoj ports: - "3306:3306" networks: - oj-network oj-backend: image: qduoj-backend:v6 depends_on: - oj-db environment: DB_HOST: oj-db DB_DIRECT_ACCESS: "true" networks: - oj-network

常见报错处理:

  • version is unsupported:将docker-compose.yml首行改为version: '3.8'
  • 连接超时:检查network配置是否一致,建议显式声明网络段

2.2 直连认证配置

在backend服务的.env配置中增加:

DB_DIRECT_USER=oj_admin DB_DIRECT_PWD=$(openssl rand -base64 12) DB_WHITELIST=172.18.0.%,192.168.1.100

通过初始化脚本自动创建受限账号:

CREATE USER 'oj_admin'@'172.18.0.%' IDENTIFIED BY 'generated_password'; GRANT SELECT, INSERT, UPDATE ON qduoj.* TO 'oj_admin'@'172.18.0.%'; REVOKE DROP, ALTER, CREATE ON qduoj.* FROM 'oj_admin'@'172.18.0.%';

3. 核心操作场景与SQL示例

3.1 竞赛数据批量更新

典型场景:结束竞赛时批量标记提交记录

-- 使用事务确保操作原子性 START TRANSACTION; UPDATE submission SET contest_visible = 1 WHERE contest_id = 1032 AND submit_time BETWEEN '2023-11-20 09:00' AND '2023-11-20 12:00'; UPDATE contest SET status = 'finished' WHERE id = 1032; COMMIT;

性能对比:

  • API方式:需要发起3124次HTTP请求,耗时148秒
  • 直连方式:单条SQL执行,耗时0.8秒

3.2 多表联合统计分析

生成学生训练效果报告:

SELECT u.username, COUNT(DISTINCT s.problem_id) AS solved_problems, AVG(s.used_time) AS avg_time, MAX(c.title) AS last_contest FROM user u LEFT JOIN submission s ON u.id = s.user_id AND s.result = 'AC' LEFT JOIN contest c ON s.contest_id = c.id WHERE u.group_id = 15 GROUP BY u.id HAVING solved_problems > 0 ORDER BY solved_problems DESC;

3.3 紧急数据修复流程

案例:误删题目后的恢复步骤

  1. 连接备份数据库
  2. 导出特定题目数据
    mysqldump -h backup-db -u recovery -p qduoj problem --where="id=782" > prob_782.sql
  3. 导入生产环境
    mysql -h oj-db -u oj_admin -p qduoj < prob_782.sql
  4. 验证关联数据一致性

4. 安全防护与监控方案

4.1 访问控制矩阵

操作类型API层权限直连权限
SELECT✓✓
INSERT✓✓
UPDATE✓✓
DELETE✓✗
ALTER TABLE✗✗
CREATE INDEX✗✗

4.2 审计日志配置

在my.cnf中启用审计:

[mysqld] plugin-load-add=audit_log.so audit_log_format=JSON audit_log_policy=ALL audit_log_rotate_on_size=100M

典型日志条目示例:

{ "timestamp": "2023-11-20T08:43:21Z", "user": "oj_admin@172.18.0.3", "operation": "UPDATE", "table": "qduoj.submission", "sql": "UPDATE submission SET result='AC' WHERE id IN(?)", "affected_rows": 128 }

5. 性能优化实践

5.1 连接池配置

在backend应用的application.yml中:

spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 idle-timeout: 30000 max-lifetime: 180000 connection-timeout: 5000 connection-test-query: SELECT 1

5.2 索引优化案例

问题查询:

SELECT * FROM submission WHERE user_id=101 AND contest_id IS NOT NULL ORDER BY submit_time DESC LIMIT 50;

优化方案:

ALTER TABLE submission ADD INDEX idx_user_contest_time (user_id, contest_id, submit_time DESC);

效果对比:

  • 优化前:全表扫描,耗时1.2s
  • 优化后:索引扫描,耗时0.03s

6. 故障排查手册

6.1 常见错误代码

错误码原因解决方案
1045认证失败检查动态密码是否过期
1141权限不足确认账号GRANT权限范围
1205锁等待超时优化事务粒度,减少持有时间
2003无法连接验证网络连通性和白名单配置

6.2 慢查询分析方法

  1. 启用慢查询日志

    SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;
  2. 使用pt-query-digest分析

    pt-query-digest /var/lib/mysql/mysql-slow.log > slow_report.txt
  3. 典型优化案例:

    • 避免SELECT * 只查询必要字段
    • 将OR条件改写为UNION
    • 对JOIN字段确保有索引

7. 开发环境与生产环境差异处理

在docker-compose.override.yml中配置开发环境特例:

version: '3.8' services: oj-db: ports: - "3307:3306" environment: MYSQL_ROOT_PASSWORD: dev123 command: --skip-grant-tables

注意:生产环境绝对禁止使用skip-grant-tables参数。我们通过环境变量注入不同的compose配置文件来实现环境隔离:

# 生产环境 docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d # 开发环境 docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进