结论:论坛数据库优化的正确顺序是「先索引、再归档、最后才考虑分区」——80% 的论坛卡顿来自缺失的联合索引和无限膨胀的帖子表,分区是最后手段而不是首选方案。
论坛数据库优化应该按什么顺序做?
结论:先查慢查询定位问题 SQL,补索引通常能解决大部分卡顿;当单表行数超过 500 万且历史数据占比过半时再做归档;只有需要「按时间批量秒删」时才上分区。顺序颠倒的代价很大:先分区会把主键结构改掉,后续加索引和改字段都更麻烦;先归档而索引没建好,导出与删除过程本身就会拖垮数据库。
具体判断标准可以量化:开启 slow_query_log=ON、long_query_time=1,跑一天看慢日志;再用 SHOW TABLE STATUS 看 Data_length,帖子表超过 2GB 或 500 万行,就该考虑归档了。
帖子表到底该建哪些索引?
结论:论坛高频查询几乎都围绕「版块 + 时间倒序」「作者 + 时间」「状态 + 置顶排序」三种模式,索引照这三种建即可,不要给每个字段都单独建索引。
典型的三条:
ALTER TABLE pre_forum_post ADD INDEX idx_tid_dateline (tid, dateline);
ALTER TABLE pre_forum_thread ADD INDEX idx_fid_sticky_dateline (fid, sticky, dateline);
ALTER TABLE pre_forum_post ADD INDEX idx_authorid (authorid, dateline);
注意联合索引的顺序:等值条件在前、范围或排序字段在后。(fid, sticky, dateline) 能同时服务 WHERE fid=? ORDER BY sticky DESC, dateline DESC,但如果你写成 (dateline, fid) 就完全无效。
一个常被忽略的点:utf8mb4 下 varchar(255) 索引占 1020 字节,接近旧行格式的 767 字节上限,MySQL 5.7 默认 innodb_large_prefix=ON 放宽到 3072 字节,但长标题字段建索引仍建议用前缀索引 title(64)。结论:标题字段不要建全字段索引,前缀 64 个字符足够区分。
索引建了为什么还是慢?
结论:九成是索引失效,用 EXPLAIN 看 type 列即可确诊——出现 ALL 就是全表扫描,出现 index 是全索引扫描,都不正常。
最常见的四种失效场景:一是 LIKE '%关键词%' 前缀通配,B+ 树无法定位起点;二是索引列被函数包裹,如 WHERE FROM_UNIXTIME(dateline) > ...,应改成 WHERE dateline > 时间戳;三是隐式类型转换,字符型字段传数字会让索引直接作废;四是在选择性极低的字段(如 sticky、status 只有几个值)上单独建索引,优化器通常直接放弃。
另一个隐形杀手是 innodb_buffer_pool_size 没调。宝塔默认配置常年是 128M,而帖子表加热点索引动辄几个 G,缓存命中率低会表现为「所有查询都慢」。结论:innodb_buffer_pool_size 设为物理内存的 50%~70%,16G 内存的机器给 8G~10G 是常规值。
历史帖子怎么归档?
结论:归档不是 DELETE FROM post WHERE dateline < X 一条语句搞定,那样会产生超长事务、锁表、binlog 暴涨,必须分批执行。
可复用的流程:先 CREATE TABLE pre_forum_post_2023 LIKE pre_forum_post; 建归档表,INSERT INTO ... SELECT 迁数据,再循环删除:
DELETE FROM pre_forum_post WHERE dateline < 1672502400 LIMIT 5000;
配合脚本每批 sleep 0.5 秒,直到影响行数为 0。归档阈值建议按「两年以上无人回复的帖子」定,保留主表最近 24 个月数据,归档表按年份拆分,前台需要时再按需查询。
归档前务必确认一件事:附件表、回复表、收藏表、通知表里都有指向帖子 ID 的外键式引用(论坛一般不建真实外键,靠应用层维护),归档要么同步迁移,要么在应用层做「主表查不到则查归档表」的兜底,否则用户点进老帖会 404。Clara BBS 这类无外键约束的轻量系统尤其要注意这点,改表结构前先备份数据库。
分区表适合论坛吗?
结论:只适合超大帖子表,且必须接受两个硬约束——分区列必须包含在主键和所有唯一键里,分区表不支持外键。
按时间 RANGE 分区的写法:
ALTER TABLE pre_forum_post
PARTITION BY RANGE (dateline) (
PARTITION p2024 VALUES LESS THAN (1735689600),
PARTITION p2025 VALUES LESS THAN (1767225600),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
主键要从 (pid) 改成 (pid, dateline),因为 MySQL 要求唯一键包含分区列。分区最大的价值是清理:ALTER TABLE ... DROP PARTITION p2024 是元数据操作,毫秒级完成,比 DELETE 快几个数量级。
但它不加速点查——单条 WHERE tid=? 的效率取决于索引,跟分区无关;跨分区查询反而可能变慢。结论:如果你的目的是「按年快速删除旧数据」,分区值得做;如果目的是「让查询变快」,回去补索引。
落地检查清单
按顺序过一遍:slow_query_log 开一天找 TOP 10 慢 SQL → 逐条 EXPLAIN 确认走索引 → 补 3 条核心联合索引 → innodb_buffer_pool_size 调到内存的 50%~70% → 主表行数超 500 万启动分批归档 → 归档需求稳定成年度例行动作后再评估分区。整个过程里,索引是收益最高、风险最低的一步,分区是最后才动的那一步。