数据库版本号"幽灵递增":我的 `dbDelta` 升级脚本在 `wp_options` 里写了 5 次 `_version`,插件却以为从未更新过

插件开发 27 浏览 0 回复 返回上级

上周给 Zsens Admin 做 3.2→3.3 的数据库迁移,踩了个让我怀疑人生的坑:dbDelta 执行成功、表结构正确、_version 选项值也刷新了,但下次加载时升级逻辑又跑了一遍——而且不是跑一遍,是每次页面请求都跑,直接把某张日志表插了 400 多万条重复数据。

根因特别蠢,但藏得极深。我当时的版本检测长这样:

```php // 灾难现场,不要复制 $installed_ver = get_option( 'zsens_admin_db_version' ); if ( $installed_ver !== ZSENS_ADMIN_DB_VERSION ) { // 执行 dbDelta... update_option( 'zsens_admin_db_version', ZSENS_ADMIN_DB_VERSION ); } ```

问题出在 ZSENS_ADMIN_DB_VERSION 的定义:

```php define( 'ZSENS_ADMIN_DB_VERSION', 3.3 ); // 浮点数! ```

get_option 从数据库读出来的是字符串 "3.3"3.3 !== "3.3",PHP 的严格比较直接判假,升级逻辑永动机启动。

更坑的是,我之前用 == 的时候明明好好的,某次代码审查被同事改成 === 就炸了。浮点数、字符串、严格比较,三杀。

---

现在我定了一套"迁移三件套"规矩

1. 版本号必须是字符串,且带日期/序号双保险

```php define( 'ZSENS_ADMIN_DB_VERSION', '20240831-003' ); // 永不歧义 ```

拒绝纯数字、拒绝浮点。日期前缀还能一眼看出这版迁移是哪天的,排查时不用翻 git。

2. 升级函数必须幂等,dbDelta 只是起点

dbDelta 本身对字段变更有防护,但对数据迁移(比如拆表、改枚举值、重建索引)完全不设防。我的做法是升级函数内部再包一层版本分段:

```php function zsens_upgrade_routine() { $current = get_option( 'zsens_admin_db_version', '0' ); $target = ZSENS_ADMIN_DB_VERSION; if ( version_compare( $current, $target, '>=' ) ) { return; // 真正的短路,不是伪短路 } // 分段迁移,每段独立事务 if ( version_compare( $current, '20240815-001', 'prefix}zsens_%'" ); if ( empty( $remaining ) ) { delete_option( 'zsens_admin_db_version' ); } // 如果还有残留表,版本号留着当"犯罪现场标记" } ```

这样重装时如果检测到 uninstalled- 前缀,就知道上次没卸干净,可以走修复流程而不是假装全新安装。

---

一个额外发现:dbDelta 的"沉默失败"

有次升级脚本里我把 KEY 写成了 INDEX(MySQL 支持,但 dbDelta 的解析器不认),dbDelta 返回空数组、没报错,表也建了,只是索引没加上。后续查询慢到超时,我以为是数据量问题,查了一下午执行计划才发现索引缺失。

现在我的升级函数必带 dbDelta 结果审计:

```php require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $result = dbDelta( $sql ); foreach ( $result as $table => $feedback ) { if ( stripos( $feedback, 'error' ) !== false ) { error_log( "zsens dbDelta fail: {$table} => {$feedback}" ); // 抛异常或发告警,绝不能静默吞掉 } } ```

dbDelta 的返回值是个关联数组,Created table xxxxxx already exists 都算正常,但如果出现 error 字样或者某张表完全没出现在返回值里,那就是有问题。

---

你们有没有遇到过更阴间的版本号坑?比如多站点环境下 get_blog_optionget_site_option 混用导致子站点版本号"夺舍"主站点?我下一个要攻的就是这个。

评论0
回复 · 0
还没有回复
微信客服 微信客服