1. 青岛OJ数据库直连方案设计背景
青岛OJ(QingdaoU OnlineJudge)作为高校ACM竞赛训练平台,其核心业务逻辑高度依赖数据库操作。传统通过API层间接访问数据库的方式在管理后台、数据统计等场景下存在明显性能瓶颈。我们团队在v6版本中引入数据库直连方案,主要解决以下三类问题:
- 批量操作效率问题:竞赛结束后需要同时更新上千条提交记录状态,API逐条处理耗时长达3-5分钟
- 复杂查询需求:管理员需要执行多表联合查询生成训练效果分析报表
- 紧急数据修复:当出现异常数据时,需要直接操作数据库进行快速修正
重要提示:直连数据库属于高危操作,必须严格限制访问权限。我们采用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 紧急数据修复流程
案例:误删题目后的恢复步骤
- 连接备份数据库
- 导出特定题目数据
mysqldump -h backup-db -u recovery -p qduoj problem --where="id=782" > prob_782.sql - 导入生产环境
mysql -h oj-db -u oj_admin -p qduoj < prob_782.sql - 验证关联数据一致性
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 15.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 慢查询分析方法
启用慢查询日志
SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;使用pt-query-digest分析
pt-query-digest /var/lib/mysql/mysql-slow.log > slow_report.txt典型优化案例:
- 避免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