[审计]FlintHub 插件性能与索引审计报告

👑Lv.11 元老 🌞 骨干会员
2026-10-25 00:16:46

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:暂存20260913/docs/插件性能与索引审计报告 (2).md。

FlintHub 插件性能与索引审计报告

审计日期:2026-08-13

范围:15 个插件目录(14 启用 + invite 停用)全部 PHP 文件

方法:① 静态扫描 JOIN 语句 → ② 核对实际 SQLite 库索引(PRAGMA index_list)→ ③ 对每处联表查询实测 EXPLAIN QUERY PLAN

结论:未发现会显著拖慢页面的缺索引联表查询;3 处查询存在可优化点(详见第四节),建议但非必须补索引


一、静态扫描结果:全部联表查询清单

遍历 15 个插件目录,共发现 7 处 JOIN 语句(分布在 4 个插件):

#插件位置SQL 摘要查询库
1medalPlugin.php:184 getUserMedals()user_medals um JOIN medals m ON um.medal_id = m.id WHERE um.user_id = :uid ORDER BY um.created_at DESCmedal 独立库
2medalPlugin.php:201 getWearingMedals()user_medal_wears w JOIN medals m ON w.medal_id = m.id WHERE w.user_id = :uid AND m.status = 1 ORDER BY w.wear_sort ASCmedal 独立库
3medalhook/controller_view_before.php:38user_medal_wears w JOIN medals m ON w.medal_id = m.id WHERE w.user_id IN (...) AND m.status = 1 ORDER BY w.user_id, w.wear_sortmedal 独立库
4dicePlugin.php:43 getLeaderboard()points_log pl JOIN users u ON pl.user_id = u.id WHERE pl.reason = '摇骰子投注' OR pl.reason = '摇骰子中奖' GROUP BY pl.user_id ORDER BY total_profit DESC LIMIT 10核心库
5seohook/layout_head_end.php:48threads t LEFT JOIN users u ON t.user_id = u.id WHERE t.id = :id AND t.deleted_at IS NULL核心库 ⚠️
6seohook/layout_head_end.php:68blogs b LEFT JOIN users u ON b.user_id = u.id WHERE b.id = :id核心库
7user_profilePlugin.php:112 getBlogs()blogs b LEFT JOIN blog_categories bc ON b.category_id = bc.id WHERE b.user_id = :uid ORDER BY b.created_at DESC LIMIT 10核心库

其余 11 个插件(announcements / content_review / daily_checkin / edit_info / friend_links / icon_generator / invite / notifications / post_favorite / quiz_duel / red_packet)零 JOIN——迁移后均已改为「独立库查询 + 核心库批量补字段」,无跨库联表。


二、已建索引核对(PRAGMA index_list 实测)

medal 独立库 plugins/medal/data/medal.sqlite

表现有索引
medals无索引(仅 id INTEGER PRIMARY KEY,JOIN 走主键 ✓)
user_medalssqlite_autoindex_user_medals_1 UNIQUE(user_id, medal_id)
user_medal_wearssqlite_autoindex_user_medal_wears_1 UNIQUE(user_id, wear_sort);sqlite_autoindex_user_medal_wears_2 UNIQUE(user_id, medal_id)

核心库 data/meta/business.sqlite(dice/seo/user_profile 联查所在库)

表现有索引
points_logidx_points_log_user (user_id)
users主键 + idx_users_email(email)、idx_users_username(username)
threads主键 + idx_threads_created(created_at)、idx_threads_category(category_id)
blogsidx_blogs_user (user_id)
blog_categories无索引(仅主键,JOIN 走主键 ✓)


三、EXPLAIN QUERY PLAN 实测执行计划

✅ 执行计划健康(无 SCAN / 无临时B树,无需处理)

查询执行计划判定
#2 medal getWearingMedalsSEARCH w USING INDEX sqlite_autoindex_user_medal_wears_1 (user_id=?) + SEARCH m USING INTEGER PRIMARY KEY✓ 全走索引
#3 medal hook IN 查询SEARCH w USING INDEX sqlite_autoindex_user_medal_wears_1 (user_id=?) + SEARCH m USING INTEGER PRIMARY KEY✓ 全走索引
#5 seo 帖子详情SEARCH t USING INTEGER PRIMARY KEY + SEARCH u USING INTEGER PRIMARY KEY LEFT-JOIN✓ 全走索引
#6 seo 博客详情SEARCH b USING INTEGER PRIMARY KEY + SEARCH u USING INTEGER PRIMARY KEY LEFT-JOIN✓ 全走索引

⚠️ 存在优化空间(USE TEMP B-TREE / 索引扫描)

查询执行计划问题
#1 medal getUserMedalsSEARCH um USING INDEX ..._user_medals_1 (user_id=?) ✓ + SEARCH m USING INTEGER PRIMARY KEY ✓ + USE TEMP B-TREE FOR ORDER BY ★WHERE 已走索引,但 ORDER BY um.created_at 无索引 → 需要临时B树排序(数据量小,开销可忽略)
#4 dice getLeaderboardSCAN pl USING INDEX idx_points_log_user ★ + SEARCH u USING INTEGER PRIMARY KEY ✓ + USE TEMP B-TREE FOR ORDER BY ★该查询按 reason 过滤,而 idx_points_log_user 是按 user_id 建的,无法用于 reason 过滤 → 全索引扫描 + 聚合排序
#7 user_profile getBlogsSEARCH b USING INDEX idx_blogs_user (user_id=?) ✓ + SEARCH bc USING INTEGER PRIMARY KEY LEFT-JOIN ✓ + USE TEMP B-TREE FOR ORDER BY ★WHERE 已走索引,但 ORDER BY b.created_at 无索引 → 临时B树排序


四、缺失索引汇总与建议修复语句

优先级库表缺失字段影响查询建议 CREATE INDEX(幂等)
P2(建议)medal 独立库user_medals(user_id, created_at)#1 勋章列表排序CREATE INDEX IF NOT EXISTS idx_user_medals_user_created ON user_medals (user_id, created_at DESC);
P1(建议)核心库points_logreason#4 骰子排行榜过滤CREATE INDEX IF NOT EXISTS idx_points_log_reason ON points_log (reason);
P2(建议)核心库blogs(user_id, created_at)#7 个人主页博客排序CREATE INDEX IF NOT EXISTS idx_blogs_user_created ON blogs (user_id, created_at DESC);

说明:三处均为「WHERE 已走索引、仅排序/过滤字段缺索引」的轻量问题,当前数据量下每次排序几十行内,不影响 0.02s 级别的页面性能。若追求极致可补,但不补也无风险。


五、附注(非性能问题,顺带发现)

  • seo#5 查询已退役表:layout_head_end.php 帖子详情分支查的是 business.sqlite 的 threads 表,而 SplitDB 迁移后帖子已迁至 main_index(threads 表 0 行)→ 该分支实际查不到数据,SEO 帖子描述长期为空。建议后续改为经 main_index.topic_index 读取(属功能修复,不在本次索引审计范围)。

六、结论

  • 无致命缺索引联表查询:7 处 JOIN 中 4 处执行计划完全健康,3 处仅有「排序/过滤字段缺索引」的轻微优化空间
  • 页面 0.02s 的开销主因不是 JOIN 索引,而是 14 个启用插件的 hook 各自产生的固定查询开销(每页多 10+ 条简单查询)——如需进一步压时,应从「减少 hook 次数 / 合并批量查询 / 结果缓存」入手,而非补索引
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 3 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录