本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2026年08月08日 统计字数: 16927字 阅读时间: 34分钟阅读 本文链接: https://soulteary.com/2026/08/08/wordpress-sqlite-docker-image-packaging-details-two-years-later.html ----- # WordPress SQLite Docker 镜像封装细节:两年后的升级 两年前,我用一个不到三十行的 Dockerfile,把 WordPress、SQLite 和官方镜像拼到了一起。两年后,这个 Dockerfile 明显变长了。SQLite 的接入方式没有变化,新增代码主要集中在跨架构发布、旧数据卷升级,以及运行状态诊断。 ## 写在前面 2024 年的《[WordPress SQLite Docker 镜像封装细节](https://soulteary.com/2024/04/21/wordpress-sqlite-docker-image-packaging-details.html)》主要讲了三件事:继续使用 WordPress 官方镜像,把准备好的文件放进 `/usr/src/wordpress`,再通过 `wp-content/db.php` 和 MU 插件接管数据库层。 这些做法今天仍然奏效。只是当年常写的“不需要数据库”,现在看容易让人误会。这个镜像仍然使用数据库,只是省去了单独运行的 MySQL 或 MariaDB 服务。外部数据库地址、账号和独立服务进程都省了,备份也可以跟着站点目录一起处理。对于个人博客、小型内容站、演示环境和开发测试,这样会省事不少。 SQLite 是面向单机本地存储的嵌入式数据库,WordPress 插件里也有不少 MySQL 专用写法。镜像依靠 `db.php` 接管 `$wpdb`,再完成查询解析和翻译。具体某个插件能否正常使用,最后还是要实际跑一遍。 这篇文章继续拆解 [soulteary/docker-sqlite-wordpress](https://github.com/soulteary/docker-sqlite-wordpress)。当前项目已经从 WordPress 6.5.2、PHP 8.3 和 SQLite Integration 2.1.9,升级到 WordPress 7.0.2、PHP 8.5 和 SQLite Integration 3.0.0-rc.8。下面从新版 Dockerfile 开始,把新增的实现和几个容易忽略的限制说清楚。 本文基于 2026 年 8 月 8 日更新后的 [`7.0.2-plugin-v3.0.0-rc.8`](https://github.com/soulteary/docker-sqlite-wordpress/tree/7.0.2-plugin-v3.0.0-rc.8) 标签,对应代码提交 [`0d476ef`](https://github.com/soulteary/docker-sqlite-wordpress/commit/0d476ef1d1700753d2ea63898f42501e90df5fdc)。未来项目继续更新时,请以仓库中的实际代码为准。 WordPress [7.0.3 版本](https://wordpress.org/news/2026/08/wordpress-7-0-3-release/)目前刚刚发布,但写下本文时还没有进入 [Official Docker Images 发布索引](https://github.com/docker-library/official-images/blob/master/library/wordpress)。官方基础镜像就绪后,项目还会更新构建并发布新版本。如果你等不及,可以使用当前的版本,在启动后的 WordPress 后台直接点击升级。 ### 这个方案适合谁? 选不选 WordPress SQLite,主要还是看站点的读写方式和运维要求。单机、读多写少时,它能省下不少工作;多实例和高并发写入场景,MySQL、MariaDB 或 PostgreSQL 更稳妥。 适合优先尝试的场景: - 个人博客、作品集、小型内容站和文档站。 - 本地开发、自动化测试和临时演示环境。 - 单机内部工具、离线环境和边缘设备。 - 希望减少 MySQL 运维成本,又愿意验证插件兼容性的团队。 需要谨慎评估的场景: - 写请求很多,或者存在长事务和高频后台任务。 - 多个 WordPress 实例需要共同写入一份数据库。 - 数据库目录位于 NFS 等网络文件系统。 - 业务依赖复杂 MySQL 特性,或者有严格的数据库 HA、审计和恢复要求。 ## 快速开始 可以从 Docker Hub 或 GHCR 拉取镜像: ```bash # Docker Hub docker pull soulteary/sqlite-wordpress:7.0.2-plugin-v3.0.0-rc.8 # GitHub Container Registry docker pull ghcr.io/soulteary/sqlite-wordpress:7.0.2-plugin-v3.0.0-rc.8 ``` 这次诊断功能更新沿用了现有长标签。机器上如果缓存过同名镜像,请主动执行一次 `docker pull`。 仓库里的 `7.0.2` 和 `7.0.2-plugin-v3.0.0-rc.8` 是两种不同的镜像。[`7.0.2` 对应的 Dockerfile](https://github.com/soulteary/docker-sqlite-wordpress/blob/7.0.2/Dockerfile)使用 SQLite Integration 2.2.17,尚未包含 3.0 RC、Rust 原生扩展、根目录 UI Loader 和自愈入口。复现本文内容时,请使用 `7.0.2-plugin-v3.0.0-rc.8`。 接着创建一个本地目录,并启动容器: ```bash mkdir -p wordpress docker run -d \ --name sqlite-wordpress \ --restart unless-stopped \ -p 127.0.0.1:8080:80 \ -v "$(pwd)/wordpress:/var/www/html" \ ghcr.io/soulteary/sqlite-wordpress:7.0.2-plugin-v3.0.0-rc.8 ``` 浏览器访问 `http://localhost:8080`,按照 WordPress 的引导完成初始化即可。安装过程中不需要填写 MySQL 服务器地址、用户名和密码。 如果你更习惯 Docker Compose,可以使用下面的配置: ```yaml services: wordpress: image: ghcr.io/soulteary/sqlite-wordpress:7.0.2-plugin-v3.0.0-rc.8 container_name: sqlite-wordpress restart: unless-stopped ports: - "127.0.0.1:8080:80" volumes: - ./wordpress:/var/www/html ``` ```bash docker compose up -d ``` 完成安装后,登录后台,打开「工具 → SQLite Diagnostics」,可以核对原生扩展、SQLite 引擎、连接参数、数据库文件占用、PHP 环境、drop-in 和集成插件版本。「工具 → SQLite id key fix」则用于检查后文提到的 `id` 结果键兼容修复。 ## 两年间发生了什么 2024 年的基准是文章发布当天的 [`4556e85`](https://github.com/soulteary/docker-sqlite-wordpress/commit/4556e85380cf1b304fcf47af5b348cbd3413d7b9),两版之间的完整差异可以在 [GitHub Compare](https://github.com/soulteary/docker-sqlite-wordpress/compare/4556e85380cf1b304fcf47af5b348cbd3413d7b9...0d476ef1d1700753d2ea63898f42501e90df5fdc) 中查看。 | 项目 | 2024 年 4 月 | 2026 年 8 月 | | ------------------ | ------------------- | ----------------------------------- | | WordPress | 6.5.2 | 7.0.2 | | PHP | 8.3 | 8.5 | | SQLite Integration | 2.1.9 | 3.0.0-rc.8 | | 上游结构 | 单插件仓库布局 | Monorepo、多 Package | | 镜像构建 | 单阶段下载压缩包 | Rust Builder + 最终运行镜像 | | 查询解析 | 纯 PHP | amd64/arm64 原生扩展,其他架构回退 PHP | | 数据卷初始化 | 依赖官方入口首次复制 | 每次启动对账 SQLite 关键文件 | | 运行诊断 | 主要依赖容器命令 | 后台提供只读 SQLite Diagnostics 页面 | | 兼容处理 | 上游插件 | 为部分单表 `SELECT` 补充显式列别名 | | 多架构发布 | 单 Runner 通过 QEMU 构建 | 原生 amd64/arm64 + 32 位 ARM QEMU 并行构建 | 两年前的实现首要目标是“保持足够简单”。这次代码主要增长在几个地方:上游换了目录结构,Rust 扩展需要重新安排多架构构建,旧数据卷也暴露了升级问题。下面逐个看。 ## 核心做法没有变化:用 `db.php` 接管数据库层 这个项目始终基于 [Docker Official Image for WordPress](https://hub.docker.com/_/wordpress) 构建。最终运行环境里的 Apache、PHP、WordPress 核心和官方入口逻辑,仍然来自上游镜像。 SQLite 的接入点位于 WordPress 加载数据库对象的位置:`wp-content/db.php`。WordPress 会优先加载这个特殊的数据库 drop-in。SQLite Integration 在这里创建一个兼容 `wpdb` 的实现,将 WordPress 和插件发来的 MySQL 查询交给解析器,再翻译成 SQLite 能执行的语句。 ```mermaid flowchart TD A["WordPress 与插件发出 MySQL 查询"] --> B["db.php 接管 wpdb"] B --> C["MySQL Lexer 与 Parser"] C --> D["SQL 翻译与 MySQL 行为模拟"] D --> E["PDO SQLite 与数据库文件"] ``` 查询先经过 Lexer 和 Parser,再由驱动翻译成 SQLite 能执行的语句。SQLite 并不认识 `SHOW TABLES`、`ALTER TABLE` 的许多 MySQL 写法,也不会天然提供完全相同的类型、排序规则和返回值语义。 [SQLite Database Integration 3.0](https://github.com/WordPress/sqlite-database-integration/blob/v3.0.0-rc.8/README.md) 已经整理成 Monorepo,分别维护 MySQL Lexer、Parser、SQLite Driver、MySQL Proxy、WordPress Plugin 和测试套件。它正常要求 SQLite 3.37.0 或更高版本;最低到 3.27.0 的 Legacy Mode 需要显式启用 `WP_SQLITE_UNSAFE_ENABLE_UNSUPPORTED_VERSIONS`,不适合作为日常部署路径。 目前其他插件和 SQLite 插件兼容性仍然需要靠实测。插件上游还在处理[字符集与排序规则](https://github.com/WordPress/sqlite-database-integration/issues/456)、[部分 MySQL 专有语法](https://github.com/WordPress/sqlite-database-integration/issues/454),以及 [GIS、Dump 等能力](https://github.com/WordPress/sqlite-database-integration/issues)。有一些插件会绕开 `$wpdb`,[直接建立 `mysqli` 连接](https://make.wordpress.org/core/2023/04/19/status-update-on-the-sqlite-project/),这些查询根本不会经过 `db.php`。部署前最好把实际使用的插件、主题、后台任务和导入导出流程都跑一遍。 ## 为什么还需要一个根目录 Loader 两年前的文章里,我们说把 SQLite Integration 放进 `wp-content/mu-plugins`,WordPress 就会自动启用它。这个说法省略了一个重要细节:[Must-Use Plugins](https://developer.wordpress.org/advanced-administration/plugins/mu-plugins/) 只会自动加载 `mu-plugins` 根目录中的 PHP 文件,不会递归寻找子目录里的入口文件。 SQLite 数据库驱动不受这个问题影响,因为它通过 `wp-content/db.php` 直接加载。但上游插件自己的 `sqlite-database-integration/load.php` 还负责设置页面、健康检查和管理界面;如果没有根目录入口,这部分 UI 就不会自动挂载。 当前镜像因此增加了一个只有几行代码的 [`sqlite-database-integration-loader.php`](https://github.com/soulteary/docker-sqlite-wordpress/blob/0d476ef1d1700753d2ea63898f42501e90df5fdc/sqlite-database-integration-loader.php): ```php /usr/local/etc/php/conf.d/wp_mysql_parser.ini ; \ else \ rm -f /usr/local/lib/php/extensions/wp_mysql_parser.so ; \ fi ``` 发布流水线也随之调整。当前 [GitHub Actions](https://github.com/soulteary/docker-sqlite-wordpress/tree/0d476ef1d1700753d2ea63898f42501e90df5fdc/.github/workflows) 使用原生 x86 Runner 构建 amd64、原生 ARM Runner 构建 arm64,三种 32 位 ARM 才启用 QEMU。各架构分别推送到 Docker Hub 和 GHCR,最后用目标 Registry 返回的 Digest 合并 Multi-Arch Manifest。 两个 Registry 的 Digest 分开保存,因为某个 Digest 已经存在于 Docker Hub,并不能证明 GHCR 中也有同一个对象。当前同一次 Tag Push 还会同时触发版本标签和 `latest` 两条发布流程,五个平台又分别为 Docker Hub 与 GHCR 构建,因此一轮发布存在不少重复工作。 ## 后台可以直接看 SQLite 运行状态 项目增加了 [`sqlite-diagnostics.php`](https://github.com/soulteary/docker-sqlite-wordpress/blob/0d476ef1d1700753d2ea63898f42501e90df5fdc/sqlite-diagnostics.php)。Dockerfile 将它复制到 `wp-content/mu-plugins` 根目录,WordPress 会自动加载,并在后台「工具」菜单中注册一个 `SQLite Diagnostics` 页面。 管理员打开页面后,可以查看六组信息: | 分组 | 显示内容 | 主要用途 | | ------------------------ | --------------------------------------- | ----------------- | | Native Extension | 扩展、原生 Lexer、原生 Parser 和当前解析路径 | 检查原生扩展及 PHP 回退状态 | | SQLite | `sqlite_version()`、`sqlite_source_id()` | 核对实际链接的 SQLite 引擎 | | PRAGMA (live connection) | 日志模式、同步级别、锁等待、页、缓存、外键和检查点参数 | 查看当前连接的运行配置 | | Storage | Main、WAL、SHM 的路径、大小和合计 | 观察相关文件的即时磁盘占用 | | Environment | PHP、CPU、`pdo_sqlite`、drop-in 和数据库路径 | 判断镜像与运行环境是否符合预期 | | Integration Plugin | SQLite Integration 版本 | 核对卷内插件版本 | 原生路径判断会分别检查 `WP_MySQL_Native_Lexer` 和 `WP_MySQL_Native_Parser`,可以区分完整原生路径、部分原生路径和纯 PHP 回退。PRAGMA 组读取的是 WordPress 当前 SQLite 连接,重点可以看 `journal_mode`、`synchronous`、`busy_timeout` 和 `wal_autocheckpoint`。 页面还会分别统计主数据库、WAL 和 SHM 文件。它们会随着写入和检查点变化,因此页面展示的是打开时的状态快照。文件合计反映磁盘占用,不等于逻辑数据量,也不能作为备份完整性的依据。 这个页面适合排查版本错配、架构回退、扩展漏装、WAL 增长和连接参数。它不会执行完整性检查,也不记录页面耗时、慢查询和 SQL 翻译结果;遇到具体的业务查询问题,仍要结合日志,必要时另行安装 Query Monitor。 ## 从 `id` 和 `ID` 的大小写问题继续往下查 MySQL 与 SQLite 的差异有时会藏在结果集的键名里。WordPress 的文章表将主键声明为大写的 `ID`,某些插件却会写出下面这样的查询: ```sql SELECT P.id FROM wp_posts AS P; ``` MySQL 通常按照查询中写出的形式返回键名 `id`。SQLite 返回未显式取别名的列时,会使用真实声明名 `ID`。我用一个只包含 `ID` 字段的最小 SQLite 表复核,`SELECT P.id` 的结果元数据确实仍是 `ID`。如果插件接下来读取 `$item['id']` 或 `$row->id`,在 MySQL 中正常,在 SQLite 中却可能得到空值。 仓库为此增加了 [`sqlite-select-id-key-fix.php`](https://github.com/soulteary/docker-sqlite-wordpress/blob/0d476ef1d1700753d2ea63898f42501e90df5fdc/sqlite-select-id-key-fix.php)。早期版本依赖 SQLite Integration 2.x 的 `pre_query_sqlite_db` Hook,3.0.0-rc.8 已经没有这个调用点。随后提交的 [`edce047`](https://github.com/soulteary/docker-sqlite-wordpress/commit/edce047a0b6ffc9fe0be9cfa2ae1d9af6c967382) 保留旧版兼容,同时接入 WordPress 的 `query` Filter: ```php add_filter( 'query', 'sqlite_select_id_key_fix_rewrite_query', 10, 1 ); ``` rc.8 会在查询进入 Driver 前经过这个 Filter,前面的查询会变成: ```sql SELECT P.id AS "id" FROM wp_posts AS P; ``` 显式别名把结果键固定为查询中写下的 `id`。当前改写主要覆盖结构简单的单表 `SELECT`;`DISTINCT`、`JOIN`、`UNION`、子查询、函数表达式等复杂查询仍会原样交给上游。插件作者如果能直接修改 SQL,显式写出 `SELECT P.ID AS id` 会更稳妥。 最新的 [`0d476ef`](https://github.com/soulteary/docker-sqlite-wordpress/commit/0d476ef1d1700753d2ea63898f42501e90df5fdc) 又在后台「工具」菜单中增加了 `SQLite id key fix` 页面。它会显示 Filtered SQL,并用同一条 `SELECT P.id` 分别检查 `ARRAY_A` 和 `OBJECT` 两种结果。站点中至少有一篇文章时,可以同时看到查询已改写为 `P.id AS "id"`,以及两种结果中是否出现了小写 `id`。这个探针只读取文章表,不会写入数据库。 ## 旧数据卷为什么会报数据库连接错误 2024 年文章专门解释过,为什么构建时要修改 `/usr/src/wordpress`,不能直接修改 `/var/www/html`。 WordPress 官方镜像把 `/usr/src/wordpress` 当作镜像内的程序源,把 `/var/www/html` 当作实际运行目录。官方 [`docker-entrypoint.sh`](https://github.com/docker-library/wordpress/blob/f4cb99ee5e59ae4eccd18502b29d8f987b2455c7/docker-entrypoint.sh) 只在运行目录尚未安装 WordPress 时,才会将程序复制进去。 新卷启动没有问题,麻烦出在升级旧卷时: 1. 用户已经有一个初始化完成的 `./wordpress` 目录。 2. 用户更换了新的 SQLite WordPress 镜像。 3. 官方入口看到 WordPress 已经存在,不再执行完整复制。 4. 新镜像中的 `wp-content/db.php` 或 MU 插件没有进入旧卷。 5. WordPress 找不到 SQLite drop-in,回到默认的 MySQL 路径,最后显示“Error establishing a database connection”。 数据库文件通常还在,缺的是负责加载 SQLite 的 `db.php` 和 MU 插件。 当前版本新增的 [`docker-entrypoint-sqlite.sh`](https://github.com/soulteary/docker-sqlite-wordpress/blob/0d476ef1d1700753d2ea63898f42501e90df5fdc/docker-entrypoint-sqlite.sh) 会在每次启动时完成这些工作: 1. 调用官方的 `docker-ensure-installed.sh true`,完成标准初始化和配置生成。 2. 对比镜像源目录与运行目录中的 `wp-content/db.php`,缺失或变化时覆盖。 3. 将镜像提供的 MU 插件同步到运行目录。 4. 确保默认数据库目录和 `.ht.sqlite` 存在。 5. 以 root 身份启动时,尽力修正 `www-data` 所有权和必要权限。 6. 最后用 `exec` 启动原始的 Apache 命令。 现在流程分成两段:构建时准备 `/usr/src/wordpress`;容器启动后,再把 SQLite 关键文件同步到挂载卷。 脚本只维护 SQLite drop-in、配套 MU 插件和默认数据库目录,不会删除用户安装的主题、普通插件和上传文件,也不负责升级卷内的 WordPress 核心。镜像提供的 MU 插件应视为镜像管理文件;自己的 MU 插件最好使用独立文件名,避免下一次启动时被覆盖。 ## 挂载卷的权限怎么处理 SQLite 没有独立的数据库服务账号,但运行 WordPress 的 PHP 进程必须能够创建和写入数据库文件、WAL 文件以及共享内存文件。 当前 Dockerfile 会预先准备这些常用目录: ```text wp-content/database wp-content/plugins wp-content/themes wp-content/uploads wp-content/upgrade ``` 随后将 `wp-content` 交给 `www-data:www-data`,目录设置为 `755`,默认数据库文件 `.ht.sqlite` 设置为 `640`。启动脚本还会针对挂载卷再次进行尽力而为的修正。 这里和上游 rc.8 有一个细微差别:上游只在数据库目录不存在时以 `0700` 创建,并把新建的 `.htaccess`、`index.php` 设为 `0600`;本镜像提前把目录建成 `0755`,主要为了适配 `www-data` 和 bind mount。 使用 bind mount 时,容器中的 UID/GID 与宿主机文件所有权仍然可能冲突。如果日志中出现 `readonly database`、`unable to open database file`,或者无法创建 `-wal`、`-shm` 文件,先检查宿主目录权限,别急着重装 WordPress: ```bash docker exec sqlite-wordpress id www-data docker exec sqlite-wordpress ls -ld /var/www/html/wp-content/database docker exec sqlite-wordpress ls -la /var/www/html/wp-content/database ``` 启动脚本只有在容器以 root 启动时才能执行 `chown`。如果使用非 root 用户、只读文件系统或更严格的容器安全策略,需要提前准备一个可写的数据目录。 ## 数据库文件放在 Web 目录里安全吗 默认数据库文件仍然是: ```text /var/www/html/wp-content/database/.ht.sqlite ``` 当前镜像固定使用 Apache。Apache 的默认配置会拒绝直接访问以 `.ht` 开头的文件,上游插件还会在数据库目录中创建拒绝访问的 `.htaccess` 和用于阻止目录浏览的 `index.php`。在这个镜像的默认组合下,直接请求 `.ht.sqlite` 会被 Web 服务器拒绝。 `.ht` 文件名需要配合 Web 服务器规则才能发挥作用。如果换成 Nginx、Caddy 或自建 PHP-FPM 镜像,要重新配置数据库目录的拒绝规则。更严格的环境可以使用 `DB_DIR` 把数据库移到 Web 根目录之外: ```php define( 'DB_DIR', '/var/lib/wordpress-sqlite' ); define( 'DB_FILE', 'site.sqlite' ); ``` 使用官方镜像支持的 `WORDPRESS_CONFIG_EXTRA` 时,Compose 可以这样配置: ```yaml services: wordpress: image: ghcr.io/soulteary/sqlite-wordpress:7.0.2-plugin-v3.0.0-rc.8 restart: unless-stopped ports: - "127.0.0.1:8080:80" environment: WORDPRESS_CONFIG_EXTRA: | define( 'DB_DIR', '/var/lib/wordpress-sqlite' ); define( 'DB_FILE', 'site.sqlite' ); volumes: - ./wordpress:/var/www/html - ./database:/var/lib/wordpress-sqlite ``` 此时要确保 `./database` 对容器内的 `www-data` 可写。自愈脚本只自动处理默认的 `wp-content/database`,不会修改自定义目录的所有权。 ## 为什么 Dockerfile 里多了几条 `test` 上游进入 3.0 RC 后,文件布局变化更频繁。如果 Dockerfile 只是一连串 `cp`,目录变化有时不会让构建立刻报错,缺失文件可能要等到容器启动时才暴露。 当前构建会主动验证: - SQLite Driver 的核心文件存在。 - `wp-includes/database/load.php` 已经从链接变成真实文件。 - Query Monitor 集成需要的 `boot.php` 存在。 - 生成的 `db.php` 包含 `SQLITE_DB_DROPIN_VERSION`。 - `{SQLITE_IMPLEMENTATION_FOLDER_PATH}` 占位符已经被替换。 我很喜欢这种朴素检查:把最容易断的几个路径写死,出问题时很快就能看到。不过,文件和字符串断言无法验证运行时 Hook、PHP ABI 和查询结果,构建通过后仍要启动容器跑一次真实查询。 ## WAL 模式下怎样备份 SQLite 主要数据在 `.sqlite` 文件里,WAL 模式还会使用 `-wal` 和 `-shm`。站点运行时只复制主文件,备份可能不完整。 离线备份最省心:先停止写入,再整体打包持久化目录。 ```bash docker compose stop wordpress tar --create --gzip \ --file "wordpress-backup-$(date +%Y%m%d-%H%M%S).tar.gz" \ wordpress docker compose start wordpress ``` 如果数据库放在独立目录,也要把那个目录纳入同一份备份。 需要不停机备份时,不要直接对活跃文件执行 `cp`。SQLite 官方提供了 [Online Backup API](https://sqlite.org/backup.html),也可以使用 `VACUUM INTO` 生成一致的数据库副本。具体选哪一种,要结合数据库大小、可接受的锁等待和恢复流程决定。 升级前应把镜像引用和数据库快照配对保存。如果新版 Driver 修改了内部元数据,单独回退镜像可能无法恢复旧状态。 恢复后别只看首页,至少运行下面几项检查: ```sql PRAGMA quick_check; PRAGMA integrity_check; PRAGMA foreign_key_check; ``` `quick_check` 适合先做快速筛查,完整的 `integrity_check` 成功时返回 `ok`;它不检查外键,因此还要单独运行 `foreign_key_check`。当前镜像没有承诺内置 `sqlite3` CLI,可以使用 PHP PDO 或独立的 SQLite 工具容器完成检查。 ## WAL 能解决多少并发问题 在支持 WAL 的环境里,3.0.0-rc.8 的上游 Driver 默认配置是: - `journal_mode=WAL` - `synchronous=NORMAL` - 可写锁等待时间 10 秒 WAL 允许读取与写入并行,通常比传统回滚日志更适合 Web 请求。不过,[SQLite 官方文档](https://www.sqlite.org/wal.html)写得很清楚:同一时间仍然只有一个写事务;WAL 的共享内存机制还要求所有访问者位于同一台机器,不适合把数据库文件放在网络文件系统上让多台主机共同写入。 [rc.8 在写事务上使用 `BEGIN IMMEDIATE`](https://github.com/WordPress/sqlite-database-integration/blob/v3.0.0-rc.8/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php),可以减少读事务升级为写事务时遇到 `SQLITE_BUSY_SNAPSHOT` 的概率。另一个写事务已经持锁时,新请求最多等待默认的 10 秒,之后仍可能收到 `SQLITE_BUSY`。 SQLite 默认在 WAL 累积到约 1000 个数据库页时自动执行检查点;持续时间很长的读事务会阻止检查点向前推进,让 `-wal` 文件不断增长。因此,监控时除了主数据库文件大小,还要看锁错误、写入延迟和 WAL 文件增长。 `synchronous=NORMAL` 是性能与持久性的选择。在 WAL 模式中,事务仍保持原子性、一致性和隔离性,但机器突然断电或系统硬重启时,最近已经提交的事务可能回滚。若业务要求在电源故障后保住最近提交的数据,应进一步评估 `FULL` 的性能成本。 SQLite 官方将低到中等流量网站列为合适场景。请求量只是一个参考,读写比例和事务长度更关键。一个缓存命中率高的站点可能请求很多却很稳定;持续长事务和并发写入的站点,请求不多也会较早遇到锁竞争。 ## 还要检查实际的 SQLite 引擎 项目固定了 WordPress、PHP 和 SQLite Integration 的版本,却没有自行编译或固定 SQLite 引擎。官方 [`php:8.5-apache`](https://github.com/docker-library/php/blob/master/8.5/trixie/apache/Dockerfile) 基于 Debian Trixie,并通过系统库提供 PDO SQLite。Debian Trixie 在本文写作时提供的是 [`3.46.1-7+deb13u1`](https://packages.debian.org/trixie/libsqlite3-0),最终发布镜像链接的具体版本和发行版回补情况,仍应以诊断页与 Debian 安全公告为准。 这在 2026 年尤其值得检查。SQLite 官方披露了一个罕见但可能导致损坏的 [WAL-reset Bug](https://www.sqlite.org/wal.html#the_wal_reset_bug):上游判断 3.7.0 到 3.51.2 可能受影响,3.51.3 及以后版本已经修复,3.44.6 和 3.50.7 也提供了回补版本。触发条件是同一个 WAL 数据库至少存在两个连接,并在非常窄的时序中并发写入或执行检查点。 部署验收不能停在“SQLite 高于 3.37”这个功能兼容下限,还要核对诊断页中的实际引擎版本、Source ID 和发行版补丁状态。单独更新 WordPress 插件解决不了引擎层的问题。 ## 最后 两年之后,这个镜像仍然沿用最初的接入方式:以 WordPress 官方镜像为基础,通过 `db.php` 接管数据库层。新增代码解决的是项目走向长期维护后出现的实际问题,包括上游 Monorepo 组装、Rust 解析扩展、多架构发布、旧卷修复和运行诊断。 对于单机、小规模、读多写少的 WordPress 站点,它依然是一种省事的部署方式。真正上线前,把插件兼容、目录权限、备份恢复和 SQLite 引擎版本逐项跑通,再开始承载正式内容会更稳妥。 这篇文章就先写到这里吧。 --EOF