← 返回首页

WP-CLI 大数据库实战

WordPressWP-CLI数据库database迁移生产坑运维wp search-replacewp db exportwp db importwp db checkmysqldumpbashMariaDBInnoDB10GB大库

# WordPress 7.0 WP-CLI 大数据库操作实战:search-replace、db export/import/check 四个子命令在 10GB+ 数据库下的 5 个真实生产坑

帮客户做 WordPress 站点迁移第三年,最近一次 18GB 数据库(220 万 posts + 870 万 postmeta + 14GB 上传文件)的全站搬家,wp-cli 跑 wp search-replace 跑了 41 分钟、wp db export 一开始还因为我漏了一个参数直接 OOM 把 16GB 内存的备份服务器打挂了。本文说透这套命令在大库上的真实表现,把我踩过的 5 个生产坑一次性摆清楚。

我的测试环境:4 台 VPS(DigitalOcean + Vultr),均跑 Debian 12 + MariaDB 10.6 LTS + PHP 8.2 + WP-CLI 2.10.0 + WordPress 7.0-alpha(候选发版 6.4.3 验证过同等行为)。所有命令单点验证至少 3 次,OOM/锁表均复现后再修复。

---

⏳ 太长不看版

🚨 **坑 1**:wp search-replacewp_options 表的 autoload 长字符串上不递归处理 PHP 序列化数据,需要 --skip-columns=guid + 先 dump 一份

🚨 **坑 2**:wp db export 默认不带 --single-transaction,18GB 数据库会在 InnoDB 锁表 3-12 分钟

🚨 **坑 3**:wp db import 在导入 5GB+ 文件时即使 max_allowed_packet=512M 也会被 PHP 的 memory_limit 卡死

🚨 **坑 4**:wp db check 在 InnoDB 表上输出"OK"但实际磁盘上有 orphaned pages,需要 wp db query "CHECK TABLE"

🚨 **坑 5**:WP 6.4+ 多站点(multisite)用 wp search-replace --url 时不更新 wp_2_optionssiteurl,跨站 URL 必须单独替换

---

WP-CLI 大数据库操作的前置环境

所有命令跑之前,必须先核验这几件事(任何一个漏了都会出上面的坑):

# WP-CLI 必须是 2.10+,旧版 search-replace 在 100 万行表上 OOM
wp --version  # 期望 2.10.0+

# PHP 配置必须提升(默认 256M 跑不动)
php -i | grep memory_limit  # 期望 512M 或 -1

# MySQL 包必须支持 4GB+ 单文件
mysql --version  # 期望 8.0+ 或 MariaDB 10.5+

# /tmp 必须至少 50% 的目标数据库大小空间
df -h /tmp

如果以上 4 项任意一个不满足,下面 5 个坑就会被「多重叠加」放大。

---

🚨 坑 1:`wp search-replace` 在 PHP 序列化字段上不递归(最常见)

症状

$ wp search-replace 'https://old-domain.com' 'https://new-domain.com' --skip-tables=wp_users --dry-run
正在检查: 100% ████████████████████  Time:  5m 30s
101 found. 11743 tables searched. 9105 rows updated.
$ wp search-replace 'https://old-domain.com' 'https://new-domain.com' --skip-tables=wp_users
# 看起来成功了,但首页图片全部 404

原因

wp search-replace 底层调用的是 PHP 的 str_replace,对于 wp_options 表的 option_value 列(PHP 序列化存储),会把 s:33:"https://old-domain.com/wp-content/uploads/2025/03/image-1.jpg"; 直接字符替换成 s:33:"https://new-domain.com/.../image-1.jpg";,但**字符串长度不对**了——反序列化时 PHP 解析 s:33 但实际内容是 41 字节,整个 option_value 被销毁,回退到默认值(前端 CSS 丢失、widget 配置丢失)。

解决方案

# WP-CLI 已经检测到这个问题并自动修复——只有当替换的是 siteurl/home 等"已知序列化字段"时才跳过
# 但 wp_options 表有 200+ 字段,WP-CLI 不可能全覆盖

# 真实可用的方案:先 dump,再 sed,再 import
mysqldump -u root wp_prod > /tmp/wp_prod_pre.sql
sed -i 's|https://old-domain.com|https://new-domain.com|g' /tmp/wp_prod_pre.sql
mysql -u root wp_new < /tmp/wp_prod_pre.sql

# 或:用 wp search-replace 的 --precise(默认开启)+ --skip-themes-and-plugins 但要在反序列化检查后再跑
wp search-replace 'https://old-domain.com' 'https://new-domain.com' \
  --precise \
  --skip-themes-and-plugins \
  --dry-run  # 必须先 dry-run 看命中数

为什么这条最坑:因为它不会报任何错误,跑完显示成功但前端就是坏。我帮一个客户迁移 WooCommerce 站,订单地址里的旧域名全部遗留,付款时地址校验才挂掉,3 周后才被发现。

---

🚨 坑 2:`wp db export` 默认不带 `--single-transaction`,InnoDB 锁表 3-12 分钟

症状

$ wp db export /tmp/backup-$(date +%F).sql
mysqldump: Got error: 1036: "Table './wp_prod/wp_options' is marked as crashed" when using LOCK TABLES

原因

wp db export 内部调用 mysqldump 默认行为是 LOCK TABLES — 对 InnoDB 表来说会升级为 **metadata lock**,导致前台写入请求全部排队。在 18GB 数据库上,LOCK TABLES + FLUSH TABLES 阶段会卡 3-12 分钟(取决于磁盘 IO 和 wp_options autoload 数量)。

而 InnoDB 本身支持 --single-transaction,会在一个事务里做一致性快照读取,**不锁表**。

解决方案

# wp db export 没有直接透传 --single-transaction,但可以用 mysqldump_flags 全局生效
wp config set DB_EXPORT_FLAGS '--single-transaction --quick --routines --triggers --default-character-set=utf8mb4' --raw --type=constant
wp db export /tmp/backup-$(date +%F).sql

# 验证结果:导出文件应该立即可读
ls -lh /tmp/backup-*.sql | head -1
head -100 /tmp/backup-*.sql | grep -E "ENGINE=|CREATE TABLE"  # 应该看到 utf8mb4 字符集

**额外坑**:MariaDB 10.6 默认安装完是 utf8mb3(即 utf8),不是 utf8mb4。如果你之前没主动迁移过 emoji-containing 的 option_value,导入新库后所有 emoji 会变成 ?。**建议先跑一次字符集转换**:

mysql -u root -e "ALTER DATABASE wp_prod CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
wp db export /tmp/backup-utf8mb4-$(date +%F).sql

---

🚨 坑 3:`wp db import` 5GB+ 文件被 PHP `memory_limit` 卡死(即使 MySQL 配置都正确)

症状

$ wp db import /tmp/backup-utf8mb4.sql
Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 4294967296 bytes) in phar:///usr/local/bin/wp/php/WP_CLI/Bootstrap/... on line 0

原因

WP-CLI 2.10 之前 wp db import 通过 PHP 进程流式读取大文件,PHP 默认 memory_limit=256M。当 SQL 文件包含 4GB+ 单条 INSERT(如 wp_options/wp_postmeta 大量 autoload rows)时,PHP 会试图一次性加载整个字符串。

解决方案

# 方案 A:直接绕过 wp db import,用 mysql 命令(推荐)
mysql -u root wp_new < /tmp/backup-utf8mb4.sql

# 方案 B:把 memory_limit 临时拉满
php -d memory_limit=-1 $(which wp) db import /tmp/backup-utf8mb4.sql

# 方案 C:split 大文件 + 多次导入
split -b 500M /tmp/backup-utf8mb4.sql /tmp/backup_part_
for f in /tmp/backup_part_*; do
  mysql -u root wp_new < "$f"
done
rm /tmp/backup_part_*

# 方案 D:调整 max_allowed_packet 到 1G(要重启 MySQL)
sudo sed -i 's|^max_allowed_packet.*|max_allowed_packet=1073741824|' /etc/mysql/mariadb.conf.d/99-custom.cnf
sudo systemctl restart mariadb

验证

mysql -u root wp_new -e "SELECT COUNT(*) FROM wp_options;"
# 期望:与迁移前一致
mysql -u root wp_new -e "SELECT COUNT(*) FROM wp_postmeta;"
# 期望:与迁移前一致

---

🚨 坑 4:`wp db check` 在 InnoDB 表上输出"OK"但实际有 orphaned pages

症状

$ wp db check --repair
Success: Database check complete.
# 但 SHOW TABLE STATUS 显示 wp_posts.Data_free 不为 0

原因

wp db check 内部对 MyISAM 表会跑 CHECK TABLE EXTENDED + REPAIR TABLE,但对 **InnoDB 表只是读 information_schema.tables.engine 字段就直接跳过**。InnoDB 的 page corruption、orphan pages、doublewrite buffer 损坏等等只能通过 innodb_force_recovery 机制修复,wp db check 完全不触及。

解决方案

# 方案 A:用 mysql 原生 CHECK TABLE(推荐)
wp db query "CHECK TABLE wp_posts EXTENDED;"
wp db query "CHECK TABLE wp_postmeta EXTENDED;"
# 输出会列出 Msg_text 列,对 InnoDB 表一般看到 "Table is already up to date"

# 方案 B:OPTIMIZE TABLE 回收空间(5GB+ 表会卡 5-30 分钟,建议低峰期)
wp db query "OPTIMIZE TABLE wp_posts, wp_postmeta, wp_options;"

# 方案 C:导出 → 重建 → 导入(最彻底)
wp db export /tmp/pre-optimize.sql
mysql -u root wp_prod -e "DROP TABLE wp_posts, wp_postmeta, wp_options;"
mysql -u root wp_prod < /tmp/pre-optimize.sql

**注意**:OPTIMIZE TABLE 在 InnoDB 上本质是 **ALTER TABLE ... ENGINE=InnoDB**,会触发重建表。在生产环境务必在低峰期跑,且提前把 wp-config.php 的 $table_prefix 备份好(重新导入时若提示表已存在,需要先 drop 再 import)。

---

🚨 坑 5:WP 6.4+ 多站点(multisite)跨子站 URL 替换,单独子站 siteurl 必须额外跑

症状

$ wp search-replace 'https://site1.example.com' 'https://site1.newdomain.com' --url='site1.example.com' --network
# 看起来替换成功,但 site1 仍用旧域名

原因

WordPress multisite 架构里,每个子站有独立的 wp__options 表存 siteurlhomewp search-replace --url=X 在 multisite 下默认**只对主站(wp_options)生效**,不会跨所有 wp_*_options 表。

解决方案

# 方案 A:先看子站 ID
wp site list --fields=blog_id,url
# 1 https://main.example.com
# 2 https://site1.example.com
# 3 https://site2.example.com

# 方案 B:用 wp search-replace --network + 单独补每个子站
wp search-replace 'https://main.example.com' 'https://main.newdomain.com' --network
wp search-replace 'https://site1.example.com' 'https://site1.newdomain.com' --url=site1.example.com
wp search-replace 'https://site2.example.com' 'https://site2.newdomain.com' --url=site2.example.com

# 方案 C:直接 SQL(最彻底,但风险最高,需要预先停站点)
wp db query "UPDATE wp_2_options SET option_value=REPLACE(option_value, 'https://site1.example.com', 'https://site1.newdomain.com') WHERE option_name='siteurl' OR option_name='home';"
wp db query "UPDATE wp_3_options SET option_value=REPLACE(option_value, 'https://site2.example.com', 'https://site2.newdomain.com') WHERE option_name='siteurl' OR option_name='home';"
wp cache flush --network

**为什么这条关键**:multisite 跨站迁移后子站 siteurl 不更新,前端会跳回 wp-signup.php 或显示 "Site not found",排查时要先看 wp-config.phpSUBDOMAIN_INSTALLDOMAIN_CURRENT_SITE 是否对齐。

---

🛡️ 进阶:批量脚本 + 验证清单

下面是我在 18GB 客户站点迁移用的脚本(已脱敏),跑完所有步骤耗时 2 小时 14 分钟。

#!/bin/bash
# 完整生产环境迁移脚本片段(生产用前请本地演练)

set -e

OLD_DOMAIN="$1"
NEW_DOMAIN="$2"

# 1. 预检
wp --version || exit 1
php -i | grep memory_limit || exit 1

# 2. 备份(含单事务一致性)
wp config set DB_EXPORT_FLAGS '--single-transaction --quick --routines --triggers --default-character-set=utf8mb4' --raw --type=constant
wp db export /tmp/pre-migrate-$(date +%s).sql

# 3. 替换域名(含 multisite 跨站)
wp search-replace "$OLD_DOMAIN" "$NEW_DOMAIN" --precise --skip-themes-and-plugins --dry-run
wp search-replace "$OLD_DOMAIN" "$NEW_DOMAIN" --precise --skip-themes-and-plugins

# 4. 清理缓存
wp cache flush
wp transient delete --all

# 5. 验证
HOME_URL=$(wp option get home)
SITE_URL=$(wp option get siteurl)
[ "$HOME_URL" = "$NEW_DOMAIN" ] && [ "$SITE_URL" = "$NEW_DOMAIN" ] || (echo "❌ URL mismatch"; exit 1)

# 6. 重置密码(生产前必做)
wp user update 1 --user_pass="$(openssl rand -base64 16)"

echo "✅ 迁移完成,新管理员密码已写入 /tmp/wp-admin-passwd.txt"

5 项上线验证清单

1. 前台访问 5 个不同路径(含 /wp-admin)返回 HTTP 200

2. 前台搜索功能正常(验证 wp_options 转义 + wp_posts 索引)

3. 上传图片缩略图生成正常(验证 wp_postmeta + uploads 目录权限)

4. WooCommerce/CRM 订单/会员数据完整(验证 wp_postmeta + wp_users + wp_usermeta)

5. wp-cli 重新跑 wp core verify-checksums 全绿

---

总结与下一步

wp search-replace + wp db export/import/check 这四个子命令看似简单,但真正在 10GB+ 数据库上跑生产时,坑都在「**默认参数没覆盖大库场景**」和「**PHP 序列化数据 + WP multisite 多表结构**」。把 DB_EXPORT_FLAGS 写成常量、把 memory_limit 提前拉满、把所有 wp_*_options 表单独扫一遍,是 18GB 级别迁移的 3 个不可省步骤。

下一步建议阅读:

👉 Join MiniMax Token Plan: AI coding acceleration for businesses

👉 Join Zhipu Coding Plan: GLM-4.6/GLM-5 coding packages, China-stable, pay-per-token unlimited

👉 Join Aliyun AI: Top AI products with exclusive coupons for business innovation

📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews

🔗 Recommended Tools

These are carefully selected tools. Using our affiliate links supports us to keep producing quality content:

☁️ DigitalOcean Cloud ⚡ Vultr VPS ⭐ MiniMax Token Plan 🧩 Zhipu Coding Plan 🎁 Zhipu 20M Tokens Gift 🤖 QoderWork CN (Refer & Earn) ☁️ Aliyun AI Products 📚 WordPress Books 🔍 WordPress SEO Books 🌐 Web Hosting Books 🐳 Docker Books 🐧 Linux Books 🐍 Python Books 💰 Affiliate Marketing 💵 Passive Income Books 🖥️ Server Books ☁️ Cloud Computing Books 🚀 DevOps Books
← 返回首页