生产 WordPress 从 MySQL 迁移 SQLite:决策、实测与踩坑

一、先说清楚要做什么

本站跑在阿里云 2 核 1.8G 的 ECS 上,WordPress + MySQL 8.4 容器服役两年。这篇文章记录一次不停机的生产迁移:把数据层从 MySQL 容器换成 SQLite 单文件。目标只有一个——用实测数据回答”对小博客,这刀值不值得动”。

“生产”这个约束会让每个决策变重:不能停机、不能丢一篇内容、回滚必须分钟级可行。所以这次迁移的验收标准不是”能跑”,而是下面这张表:

指标 目标 怎么测
迁移期间博客可用性 全程在线 curl 首页状态码监控
内容一致性 203 篇逐篇 MD5 归零 双库全量比对脚本
常驻内存 MySQL 容器(~360MB)归零 docker stats
冷查询延迟 不劣化(实际反超一个量级) 同容器 PHP 基准 300 次
恢复演练 备份能真的恢复出全量内容 解压快照 + 只读校验

二、整体方案:三条路线里选一

WordPress 迁 SQLite 有三条路,选错第一条后面全是补丁:

方案 做法 优点 缺点 适用
A. 官方插件原位切换 sqlite-database-integration 插件导入,生成 db.php dropin 接管 $wpdb WordPress 官方参与维护;wp-cli、REST、所有 WP API 语义原样保留 依赖插件版本跟进 WP 版本 本站选择
B. dump 手工转换 mysqldump 导出,手工改写 DDL 后灌入 sqlite3 不依赖插件 类型映射、转义、索引全要手工核对,改漏一处就是隐性数据损坏 深度定制站
C. 双写灰度切流 期间两库双写,逐步切读 大站最稳 复杂度爆炸,个人博客杀鸡用牛刀 高流量站

反模式警告:把 mysqldump 的输出直接灌进 sqlite3 命令行。MySQL 方言里 ENGINE=InnoDB、反引号、AUTO_INCREMENT 计数都会让导入静默失败或丢索引——错了不报错,最贵。

切换前后的拓扑变化就一句话:数据从”隔壁容器里的服务”变成”PHP 进程里的一个库文件”:

# 迁移前:一次查询 = TCP + 协议 + 鉴权 + 查询
wp_xiwei (PHP) ──3306──> mysql 容器 (mysqld, ~360MB 常驻)

1. 迁移后:一次查询 = 函数调用
wp_xiwei (PHP) ──直接──> wp-content/database/wp.sqlite (8.4MB 文件)

三、一致性验证:切换成功 ≠ 迁移成功

导入完成那一刻什么都证明不了。验证用的是双库全量比对:mysqli 连 MySQL、SQLite3 只读打开新库,203 篇逐篇比 MD5。

结果是 203/203 篇都在,其中 9 篇字节级不一致。差异不等于错误——判定用的是三件套:

  1. 结构指标:表格单元格数、代码块数量双侧对比(66 = 66 ✓)
  2. 关键内容标记:新写入的小节、修复补丁在不在
  3. 损伤标记:空表格、坏标签双侧扫描

最终 9 篇全部判定为等价变换:迁移器做了转义规范化(& 与 & 这类),以及 wp_update_post 的内容过滤。语义零差异。

反模式警告:只比”文章数一致”就宣布迁移成功。行数一致但正文被过滤坏半篇,是完全可能发生的事故。

本案例的真实插曲:迁移快照恰好落在一次全站内容修复的收尾窗口之间——6 篇刚改完的文章没进快照。补救方式是用 wp_update_post 从源库把最终内容写穿到新库(走 WP API 会自动触发缓存失效)。教训:要么迁移前冻结写入,要么迁移后立刻做一次增量 diff,二选一。

四、最大的坑:三层缓存对”裸操作”完全不感知

这个站的缓存架构有三层,全部不知道”数据库换了”这件事:

请求 → W3TC 页面缓存(磁盘 file_generic)
     → W3TC dbcache(Redis,缓存 SQL 结果)
     → redis-cache 对象缓存(Redis,缓存对象)
     → 数据库(这次换了 SQLite)

迁移当天踩的最深的坑:绕过 WP 直接改数据库后,前台和 REST 迟迟”不生效”。排查链是清一层、验一次、还剩一层的窒息循环——因为两个细节:

  • 页面缓存连带查询串的 REST URL 都会缓存,你以为加个随机参数就绕开了,其实没有
  • 验证通道本身被缓存污染:看 REST 判断生效与否是不可靠的,要看前台页面 HTML

正确姿势两条,从这次之后固化为规矩:

  1. 改内容一律走 WP API(wp post update / wp_update_post / REST 写入),写穿路径自动失效三层缓存
  2. 必须裸写时,事后三层全清:rm -rf wp-content/cache + redis-cli FLUSHDB(对应库)+ apachectl graceful

另一个迁移日专属小坑:wp-cli 的 wp post term set 只认分类名不认数字 ID——传 90 进去会创建一个名字叫”90″的新分类,文章全挂错。操作密集的迁移日,这类语义坑专门咬熟手。

五、WAL 与写并发:博客负载刚好合适

SQLite 最常被质疑的是写并发。先确认模式:

PRAGMA journal_mode;  -- wal(插件已默认开启)

WAL 的语义:读不阻塞写、写不阻塞读、写与写串行。对博客负载画像——203 篇内容、读多写少的极端比例、三层缓存之下数据库只服务冷路径——单写者绰绰有余。唯一要留意的信号是 database is locked,出现时优先查 busy_timeout 而不是急着换库。

六、运维配套:三件必须跟着换的东西

库换了,围绕库的运维件不同步换,等于埋新雷:

  1. 备份反模式:裸拷贝 wp.sqlite 文件——WAL 模式下写入进行时的裸拷贝可能拿到撕裂副本。正确做法(每日 cron):
// PHP:源库只读打开,backup() 产生一致性副本
$s = new SQLite3("/var/www/html/wp-content/database/wp.sqlite",
                 SQLITE3_OPEN_READONLY);
$s->backup(new SQLite3("/tmp/db-backup.sqlite"));
  1. 恢复演练
  2. 开机自检PRAGMA integrity_check 返回 ok。

七、真实数字总账

项 MySQL 时代 SQLite 时代
常驻内存 ~360MB(独立容器,InnoDB buffer pool) 0(库嵌入 PHP 进程)
全机可用内存 257Mi 479Mi(+86%)
数据体积 数据目录 + redo/binlog(转储 1.8MB gz) 8.4MB 单文件(快照 1.5MB gz)
冷查询(主键取正文) ~0.3–1ms 级(TCP+协议+鉴权;通用参考值,迁移后已无同机对照) 0.016ms 实测(300 次均值)
恢复全库 导入 dump,分钟级 解压一个文件,秒级

诚实结论三条:

  • 性能上这是一次“免费升级”而不是”性能升级”——三层缓存下数据库只服务冷路径,0.02ms 还是 0.5ms 用户毫无感知。真正的收益是内存和运维简化。
  • 什么情况我会劝你别迁:高并发写(热文评论区爆量、WooCommerce 多写场景)、依赖 MySQL 特有 SQL 的插件、计划做多机读写分离。
  • 单文件单机是 SQLite 的天花板,也是它的地板——数据跟着一个文件走,备份、迁移、恢复从未如此简单。

八、这次迁移教会了什么

  1. 迁移的完成时刻不是切换时刻,是全量 diff 归零的时刻
  2. 缓存架构决定运维姿势:多层缓存的世界里,WP API 是唯一正门,裸 SQL 是自找的迷雾
  3. 单文件数据库的备份必须用一致性 API,裸拷贝是定时炸弹
  4. 资源受限的小机器上,”少一个 360MB 常驻进程”远比”快几毫秒”值钱
  5. 迁移日操作密度高,命令行工具的语义坑(比如 term set 认名不认 ID)专门咬熟手——重大操作前先在草稿环境过一遍

延伸阅读:《MySQL 连接数打满的定位方法》——MySQL 时代排障的代表作,如今成了时代的眼泪;《Docker 容器无限重启的排查路径》——容器化数据层的通用排障思路。

本文数字全部来自 2026-11-05 迁移当日的实测;负载不同结论会不同,但判断框架(一致性三件套、缓存认知、配套先行)是通用的。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇