先把结论列出来:
siteurl、home、active_plugins、template、stylesheet、rewrite_rules 这类核心 options,以及插件自己建的表。下面按「先量体积、再备份并验证、再逐项处理、最后验证」的顺序来。全程请在预发布环境先跑一遍。
SELECT table_name,
ROUND(data_length/1024/1024, 1) AS data_mb,
ROUND(index_length/1024/1024, 1) AS index_mb,
table_rows
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_length + index_length DESC
LIMIT 20;wp_posts、wp_postmeta、wp_options 一般排在最前面。先看清楚是哪张表大,再决定动哪张表;很多站体积大,原因来自插件自建表,而不是文章。
# 方式一:wp-cli
wp db export /tmp/wp-backup-$(date +%F-%H%M).sql --add-drop-table
# 方式二:mysqldump(表是 InnoDB 时用 --single-transaction 避免锁表)
mysqldump --single-transaction --quick --routines --triggers \
--default-character-set=utf8mb4 \
-h "$DB_HOST" -u "$DB_USER" -p "$DB_NAME" \
| gzip > /tmp/wp-backup-$(date +%F-%H%M).sql.gz只生成文件并不算验证。需要确认三件事:
ls -lh /tmp/wp-backup-*
# 未压缩的导出文件可以直接数表
zcat /tmp/wp-backup-*.sql.gz | grep -c 'CREATE TABLE'
# 和下面这个数字对比
mysql -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME'"然后实际恢复一次到临时库:
mysql -h "$DB_HOST" -u "$DB_USER" -p -e "CREATE DATABASE wp_restore_test"
zcat /tmp/wp-backup-*.sql.gz | mysql -h "$DB_HOST" -u "$DB_USER" -p wp_restore_test
mysql -h "$DB_HOST" -u "$DB_USER" -p -N -e "SELECT COUNT(*) FROM wp_restore_test.wp_posts"数字对得上,再往下走。
能删。代价是编辑器的「版本对比」历史会消失。如果站点有审计要求,或者用了依赖修订版做对比的插件,先确认再删。
SELECT COUNT(*) AS revisions FROM wp_posts WHERE post_type = 'revision';删除时要把 postmeta 一起删,否则 wp_postmeta 里会留下永远没人引用的行:
DELETE p, pm
FROM wp_posts p
LEFT JOIN wp_postmeta pm ON pm.post_id = p.ID
WHERE p.post_type = 'revision';表很大时分批跑,避免一次删太多,给 binlog 和 IO 带来太大压力:
DELETE p, pm
FROM wp_posts p
LEFT JOIN wp_postmeta pm ON pm.post_id = p.ID
WHERE p.post_type = 'revision' AND p.ID < 100000;
-- 重复执行,把 100000 往上加,直到影响行数为 0如果以后不想再产生修订版,在 wp-config.php 里加:
define('WP_POST_REVISIONS', 5);auto-draft 是编辑器打开但没保存留下的空壳,可以直接删。回收站建议先留一段时间,再按修改时间删:
DELETE p, pm
FROM wp_posts p
LEFT JOIN wp_postmeta pm ON pm.post_id = p.ID
WHERE p.post_type = 'auto-draft'
AND p.post_date < DATE_SUB(NOW(), INTERVAL 7 DAY);
DELETE p, pm
FROM wp_posts p
LEFT JOIN wp_postmeta pm ON pm.post_id = p.ID
WHERE p.post_status = 'trash'
AND p.post_modified < DATE_SUB(NOW(), INTERVAL 30 DAY);注意 post_status = 'trash' 不只包含文章,page、自定义文章类型都可能在里面,删之前先用同样的条件 SELECT 一遍看看内容。
transient 是带过期时间的临时缓存,过期后不会自动消失,会一直留在 wp_options 里。删它是安全的,代价是相关缓存要重建,短时间内站点可能稍慢。
先看规模:
SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '\_transient\_%';_transient_ 是 11 个字符,所以 SUBSTRING(option_name, 12) 取到的就是键名。先删过期的值行,再删对应的 timeout 行:
-- 1) 删已过期的 transient 值
DELETE o FROM wp_options o
JOIN wp_options t
ON t.option_name = CONCAT('_transient_timeout_', SUBSTRING(o.option_name, 12))
WHERE o.option_name LIKE '\_transient\_%'
AND o.option_name NOT LIKE '\_transient\_timeout\_%'
AND CAST(t.option_value AS UNSIGNED) < UNIX_TIMESTAMP();
-- 2) 删对应的 timeout 行
DELETE FROM wp_options
WHERE option_name LIKE '\_transient\_timeout\_%'
AND CAST(option_value AS UNSIGNED) < UNIX_TIMESTAMP();顺序不能反:先删 timeout 行,第一步就 join 不到记录了。
多站点要对每张 wp_*_options 分别执行,或者用 wp site list --field=url 循环。
不建议「把所有 transient 一次性删掉」:正在被使用、还没过期的缓存被清掉,会瞬间把请求全部发到数据库。
WordPress 每次请求都会把 autoload 为 yes/on 的 options 一次性读进内存。这类选项太多、太大,会拖慢每一个请求。
SELECT option_name, LENGTH(option_value) AS bytes, autoload
FROM wp_options
WHERE autoload IN ('yes','on')
ORDER BY bytes DESC
LIMIT 30;看到某个插件写的选项排在最前面,正确的做法是:先找插件本身有没有清理入口,或者停用插件后由它自己删;确实要保留数据又不想自动加载,再改属性:
UPDATE wp_options SET autoload = 'no' WHERE option_name = 'your_option_name';改完刷新缓存:
wp cache flush
# 多站点
wp site list --field=url | xargs -I{} wp cache flush --url={}还有一个经常被忽略的来源:装了又不用、或者卸载了却没带走自己选项的插件。卸载插件时删不删自己的 options,由插件自己决定,WordPress 没有统一保证,所以最省事的做法是装之前就想清楚。我们(AumCreate)在 wordpress.org 上放的几个免费插件 AumLang、AumViso、AumCrawl、AumDigital 也一样会往库里写东西,用不上就别装;已经装了的,先停用,看插件本身有没有提供清理数据的入口,再决定要不要动 SQL。插件清单在这里:https://aumcreate.com/plugins?utm_source=tencent&utm_medium=content&utm_campaign=tencent-wp-db-cleanup-safe-delete
不要改这几个的 autoload:siteurl、home、blogname、active_plugins、template、stylesheet、rewrite_rules、permalink_structure、db_version、upload_path、default_role。改了轻则后台路径错乱,重则站点打不开。
还要注意:autoload 字段在不同版本里可能是 yes/no,也可能是 on/off/auto,改之前先 SELECT DISTINCT autoload FROM wp_options 看一眼实际值。
父记录已经被删掉、meta 还留着,这是插件或手动删数据留下的。删之前一定先备份,因为有些插件是故意保留这些数据的。
-- 先看数量
SELECT COUNT(*) FROM wp_postmeta pm
LEFT JOIN wp_posts p ON p.ID = pm.post_id
WHERE p.ID IS NULL;
-- 确认后再删
DELETE pm FROM wp_postmeta pm
LEFT JOIN wp_posts p ON p.ID = pm.post_id
WHERE p.ID IS NULL;
DELETE cm FROM wp_commentmeta cm
LEFT JOIN wp_comments c ON c.comment_ID = cm.comment_id
WHERE c.comment_ID IS NULL;
DELETE tm FROM wp_termmeta tm
LEFT JOIN wp_terms t ON t.term_id = tm.term_id
WHERE t.term_id IS NULL;删完大量行之后,表文件不会自动变小,可以做一次表优化。InnoDB 的 OPTIMIZE TABLE 会重建表,需要大约等于表大小的额外磁盘空间,并且占用 IO,请放在低峰期:
OPTIMIZE TABLE wp_posts, wp_postmeta, wp_options, wp_comments, wp_commentmeta;然后按顺序验证:
wp db check
wp option get siteurl
wp plugin list --status=active
curl -I https://example.com/
curl -I https://example.com/wp-admin/前台、后台、登录页、一个带表单的页面各点一遍。观察 24 小时的错误日志和响应时间,再决定要不要继续清理下一项。
DROP TABLE,包括看起来「没用」的表。wp_options 里删不认识的 option_name,先搜代码看是谁在用。WHERE 的 DELETE。OPTIMIZE TABLE。清理是减法,判断标准是「删了之后站点行为不变,只是体积变小」。任何会影响前台表现的删除,都值得先在预发布环境跑一遍。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。