本文约 3,666 字,预计阅读 10 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:数据库从 80MB 涨到 900MB,站点却没多几篇文章
站点跑了两三年,数据库体积涨了十倍,文章数才多了两百篇。打开 phpMyAdmin 一看,wp_posts 里躺着几万条 revision 和 auto-draft,wp_options 里塞满了过期的 _transient_timeout_,还有一批早已卸载的插件留下的孤儿表。这些冗余数据不会让站点立刻报错,但会持续拖慢三件事:备份耗时、后台列表加载、以及数据库本身的查询效率。
Advanced Database Cleaner 这类插件的价值,就是把这些「看不见的垃圾」可视化:它能列出各类可清理项、给出数量估计,并支持按计划执行。但清理本身是有风险的——删错了表、删掉了正在使用的元数据,站点会立刻出问题。所以关键不在于「敢不敢点清理」,而在于知道每一项是什么、能不能删、删之前怎么兜底。
原理分析:冗余数据从哪来
| 冗余类型 | 成因 | 风险等级 |
|---|---|---|
| 修订版本 / 自动草稿 | 每次保存文章生成一条 revision | 低,可放心删 |
| 回收站中的文章与评论 | 删除后进入回收站,默认保留 30 天 | 低 |
| 过期 transient | 插件写入的临时缓存,带超时时间 | 低,只删过期的 |
| 孤立 postmeta | 文章被删后 meta 未清理 | 中,需确认对应关系 |
| 孤立 term relationship | 分类关联指向不存在的文章 | 中 |
| 孤儿数据表 | 卸载插件未清理表 | 中,需确认插件已不用 |
| 已失效的计划任务 | 插件卸载后 cron 事件残留 | 低 |
最需要谨慎的是「孤立」类数据:它们的判定依赖文章是否还存在,如果判定逻辑与插件不一致(比如站点用了自定义文章类型或分表),可能误删仍在使用的元数据。先备份、再小批量、最后核对,是唯一稳妥的顺序。
还有一类容易被忽略的:插件卸载后残留的计划任务。它们不会占多少空间,但每次 WP-Cron 触发时都会执行失败回调,产生日志噪音。这类问题用 WP-CLI 查看最直观。
保姆级实操:按风险从低到高清理
步骤1:先完整备份数据库。 这一步不能省,且要确认备份文件可下载、可导入。
wp db export /tmp/db-before-cleanup.sql
ls -lh /tmp/db-before-cleanup.sql
步骤2:摸清各类冗余的数量。 先看数量,再决定清不清。数量小的项目不必折腾。
-- 各类数据的数量统计
SELECT post_type, COUNT(*) AS cnt
FROM wp_posts
GROUP BY post_type
ORDER BY cnt DESC;
-- 过期 transient 的数量
SELECT COUNT(*) AS expired_transients
FROM wp_options
WHERE option_name LIKE '_transient_timeout_%'
AND option_value < UNIX_TIMESTAMP();
步骤3:清理修订版本与自动草稿。 风险最低,收益通常最大。用 WP-CLI 比在后台点更可控。
# 删除所有修订版本(数量大时分批执行)
wp post delete $(wp post list --post_type=revision --format=ids --posts_per_page=1000) --force
# 清空回收站
wp post delete $(wp post list --post_status=trash --format=ids) --force
步骤4:清理过期 transient。 只删已过期的,不要动未过期的,否则正在使用的缓存会被清掉。
-- 只删除已过期的 transient(先 SELECT 确认数量,再 DELETE)
DELETE FROM wp_options
WHERE option_name LIKE '_transient_timeout_%'
AND option_value < UNIX_TIMESTAMP();
DELETE FROM wp_options
WHERE option_name LIKE '_transient_%'
AND option_name NOT LIKE '_transient_timeout_%'
AND option_value = '';
# WP-CLI 等价做法
wp transient delete --expired
步骤5:处理孤立 postmeta。 这一项要先看清楚数量与样例,确认不是自定义文章类型在使用的元数据,再交给插件或 SQL 处理。
-- 查看孤立 postmeta 的数量(post 已不存在)
SELECT COUNT(*) AS orphan_meta
FROM wp_postmeta pm
LEFT JOIN wp_posts p ON p.ID = pm.post_id
WHERE p.ID IS NULL;
步骤6:检查残留的计划任务与孤儿表。 用 WP-CLI 看计划任务,用 SQL 看可疑的表名。
# 列出所有计划任务,找出插件卸载后残留的钩子
wp cron event list
wp cron event delete 已失效的钩子名
步骤7:配置定时清理。 用系统计划任务而不是 WP-Cron,避免受访问触发的不确定性影响;频率按月即可,不必每天跑。
# 每月 1 号凌晨 3:30 清理过期 transient 与修订版本
30 3 1 * * /usr/local/bin/wp --path=/var/www/site/public_html transient delete --expired > /dev/null 2>&1
40 3 1 * * /usr/local/bin/wp --path=/var/www/site/public_html post delete \
$(wp post list --post_type=revision --format=ids --posts_per_page=2000) --force > /dev/null 2>&1
步骤8:核对清理效果。 清理后对比表体积,并抽查前台页面与后台列表是否正常。
SELECT table_name, ROUND((data_length+index_length)/1024/1024,2) AS size_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY size_mb DESC
LIMIT 10;
常见踩坑
- 没备份就点「全部清理」。插件的批量按钮不会替你做判断,一旦删错不可逆。
- 删掉了未过期的 transient。把
_transient_*全清,导致购物车、登录态、接口缓存被清空。 - 误删孤立 postmeta。站点用了自定义文章类型或外部表时,判定可能出错。先看数量与样例再动手。
- 清理孤儿表后插件无法重装。删了表但插件目录还在,重新启用时会因为缺表而报错。确认插件确实不再使用。
- 大表 DELETE 锁表导致站点卡顿。
wp_options上百万行时,一条 DELETE 可能锁表几十秒。分批删除或放在低峰执行。 - 案例:某站点在活动高峰期用插件执行「清理所有 transient」,导致购物车会话全部失效,正在下单的用户被清空购物车,当天转化率掉了一半。整改后改为:只清理过期项、放在凌晨执行、并在活动期间禁用该计划任务。
- 清理后没有核对前台。数据库体积确实降了,但某个页面因为缺少 meta 而显示异常。清理后必须抽查关键页面。
- 把修订版本限制忘了配。清理完又会涨回来。可以结合
WP_POST_REVISIONS常量做长期控制,这与WP Crontrol 管理定时任务(Cron)里讲的「用计划任务做长期治理」是同一个思路。
总结
清理顺序应该是:备份 → 统计数量 → 清理修订版本与回收站 → 清理过期 transient → 谨慎处理孤立数据 → 检查残留计划任务与孤儿表 → 配置按月定时清理 → 抽查前台。判断该不该删的标准是:修订版本、自动草稿、已过期 transient 可以放心删;任何「孤立」类数据都要先看样例再决定。Advanced Database Cleaner 的作用是把这些项目可视化并支持定时执行,但它不能替你承担判断责任。另外,清理只是治标,长期控制修订版本数量、卸载插件时清理残留,才是治本。数据库里表单条目的留存策略,可参考表单数据存储与导出(GDPR 考量)。
