局域网答题系统 性能优化报告
日期:2026-09-15
范围:后端数据访问层(dbcore / sitedb / records / main)与前端静态资源(static / templates)
一、优化目标与关键指标
| 指标 |
目标 |
| 接口响应时间 |
核心统计接口 < 500ms(P95) |
| 数据库访问次数 |
消除列表/报告类接口的 N+1 查询 |
| 全表扫描 |
热点查询走索引,避免 SCAN |
| 首屏资源 |
qbank 页不再阻塞加载 1.3MB 富文本编辑器 |
| 静态资源传输 |
命中缓存 + GZip 压缩 |
| 无效请求 |
页面不可见时暂停轮询 |
二、测量环境与方法
- 造数规模:users=1500、qb_questions=8000、attempt_items=36000、sessions=120,库约 5.1MB。
- 两条测量路径:
- 探针层:直接对 SQLite 反复执行同一条 SQL,衡量纯查询成本(缓存热)。
- 端到端层:走 dbcore 真实路径(每次
q() = 建连 + 6 条 PRAGMA + 查询 + 关闭),衡量接口真实成本。
- 对照方法:同一场景分别在“优化前 SQL / 优化后 SQL”下多次执行取均值。
三、基线瓶颈分析
| 探针 |
场景 |
现状 |
说明 |
| A |
每请求建连开销 |
connect+PRAGMA 0.417ms,纯 connect 0.071ms |
PRAGMA 设置占建连成本约 83% |
| B |
登录态解析(每 API 必经) |
SELECT * WHERE session_token=? 0.531ms,SCAN users |
session_token 无索引 |
| C |
N+1 逐题查题干(40 题) |
逐条 19.452ms → IN 批量 0.546ms |
纯查询层 ≈35x |
| D |
sessions 校级列表 |
SCAN,无 school_id 索引 |
数据量小时耗时不显,规模增长后放大 |
| E |
qb_questions 子题展开 |
parent_id 展开 0.925ms SCAN → 加索引 0.410ms |
≈2.3x |
| F |
报告明细聚合(24k+ 行) |
SELECT * 87.0ms → 取 4 列 40.8ms → 服务端 GROUP BY 10.5ms |
宽行 + 客户端聚合是主成本 |
四、优化措施
P0 后端数据访问
- 补齐索引(
sitedb._perf_indexes):
idx_users_token(部分索引,session_token)
idx_sessions_school(sessions.school_id, id)
idx_qbq_parent(qb_questions.parent_id)
idx_qbq_school(school_id, visibility, id)
idx_att_sess_qn(attempt_items.session_id, qn_id)
idx_su_sess_score(session_users.session_id, score)
- 同时清理重复定义的
idx_users_status / idx_users_batch。
- 消除 N+1:
stats_questions、exam-dist、session_detail、user_sessions 的逐题查题干改为一次 IN (...) 批量查询。
- 列裁剪 + 合并扫描:
overview 对 attempt_items 的三次扫描合并为一次;session_users / attempt_items 去掉 SELECT *,只取所需列;get_user 每请求查询裁剪敏感/无用列。
- 连接复用(
dbcore.dbc,2026-09-16 追加):SQLite 改为线程局部连接——每线程首次使用时建连并缓存、之后长期复用,退出 dbc() 只提交/回滚不关闭。消除每查询 ~0.93ms 的建连+PRAGMA 固定开销(探针 G),并保持页缓存热态。MySQL 仍走原连接池。实测 8 线程×5 查询并发:24.6ms → 9.0ms(2.7x)。
P1 前端与传输
- 静态资源(
main.CachedStaticFiles):/static、/excelgrade 命中 Cache-Control: public, max-age=86400 + ETag,对 js/css/json/svg/txt 启用 GZip;全站资源加 ?v=<静态版本> 版本号,改文件重启即击穿旧缓存。
- 轮询暂停(
uc.js vizInterval):页面 document.hidden 时暂停定时器,重新可见时立即执行一次并恢复。应用于 exam.js(大厅)、exam_control.js(场控)、admin_rooms.js(房间)。
- wangEditor 懒加载(
qbank.js loadWang):1.3MB 编辑器脚本/样式改为首次打开编辑弹窗时动态加载(幂等 Promise),题库浏览/列表主路径不再承担首屏成本。
P2 内存与代码质量
_hb_cache 无界增长(main.py):心跳节流缓存超过 2000 条时清理 5 分钟未活跃条目。
- 死文件清理:删除
_chk_i.js。
- 重复索引定义:合并
idx_users_status / idx_users_batch 的重复建表语句。
- 逻辑冗余修复(
exam.js):筛选未提交 Excel 题由 indexOf 反查索引改为 filter((q, i) => ...) 直接用下标,避免相同题目对象误匹配。
五、优化后复测数据
端到端(dbcore 真实路径,含建连 + PRAGMA)
| 场景 |
优化前 |
优化后 |
提升 |
| stats_questions(32 查 → 2 查) |
52.57 ms |
31.09 ms |
1.7x |
| exam-dist(31 查 → 2 查) |
56.52 ms |
33.20 ms |
1.7x |
| 登录态解析(每请求必经) |
0.98 ms |
1.01 ms |
1.0x(建连开销主导) |
| overview(4 查 → 2 查) |
25.31 ms |
27.25 ms |
0.9x(宽行读取主导) |
| 排行(列裁剪 + 排序索引) |
2.97 ms |
2.97 ms |
1.0x |
纯查询层(探针)
| 场景 |
优化前 |
优化后 |
提升 |
| N+1 → IN 批量(40 题) |
19.45 ms |
0.55 ms |
≈35x |
| parent_id 展开 |
0.93 ms(SCAN) |
0.41 ms(索引) |
≈2.3x |
| 宽行 → 列裁剪(24k 行) |
87.0 ms |
40.8 ms |
≈2.1x |
| 列裁剪 → 服务端聚合 |
40.8 ms |
10.5 ms |
≈3.9x |
数据一致性验证
重构后 67 项逐字段断言(含 overview 合并扫描、IN 批量题干、列裁剪、token 解析)全部 PASS,查询结果与旧逻辑完全一致。
六、关键发现与说明
- 端到端提升被建连开销稀释:dbcore 旧实现每次
q() 都新建连接并执行 6 条 PRAGMA(约 0.42ms,其中 PRAGMA 占 83%)。N+1 消除在纯查询层是 35x,但端到端只体现为 1.7x——因为优化后查询次数锐减,建连固定成本占比反而上升。
- 已实施(2026-09-16):SQLite 线程局部连接复用(见 P0-4),每查询固定开销从 ~0.42ms 降至 ~0.001ms;并发 8 线程实测 2.7x。事务语义不变(每次 dbc() 退出仍 commit/rollback),跨线程不共享连接,回滚失败时自动丢弃缓存重建。
- overview / 登录态在测试规模下几乎无提升:小数据集下 SCAN 与索引差异被建连/宽行成本掩盖;索引收益随数据量增长而放大(机房满员 800 人 × 30 题 = 2.4 万明细行时,列裁剪与索引才是主要收益点)。
- 收益最大处:报告类接口(stats_questions / exam-dist)的 N+1 消除 + 列裁剪,以及 qbank 页 1.3MB 编辑器的首屏剥离。
七、回归验证清单
- [x] Python 3.8 / 3.12 双环境模块导入通过
- [x] 公开页
/、/exam、/profile 渲染 200
- [x] 权限场景:superadmin 访问
/qbank、/admin、/admin/rooms、/admin/stats、/admin/users 全 200
- [x] qbank 首屏 HTML 不含 wangeditor 脚本;登录后页含
STATIC_VER 注入与 sv() 版本号
- [x]
/static/qbank.js 响应头:cache-control: public, max-age=86400、ETag 存在、content-encoding: gzip
- [x] 三处轮询(exam / exam_control / admin_rooms)均使用
vizInterval
- [x] 查询结果一致性 67 项断言全 PASS
八、结论
本次优化在不改变任何业务行为的前提下,消除了统计报告接口的 N+1 查询与宽行扫描,补齐了热点查询索引,并将 1.3MB 富文本编辑器与全站静态资源做了懒加载 + 缓存 + 压缩处理。端到端核心统计接口提速约 1.7x,纯查询层最高 35x;前端首屏与轮询负载显著下降。剩余的连接复用优化已识别为后续可选项,未在本次实施以控制风险。