数据库版本号"幽灵递增":我的 `dbDelta` 升级脚本在 `wp_options` 里写了 5 次 `_version`,插件却以为从未更新过
上周给 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 的定义:
而 get_option 从数据库读出来的是字符串 "3.3"。3.3 !== "3.3",PHP 的严格比较直接判假,升级逻辑永动机启动。
更坑的是,我之前用 == 的时候明明好好的,某次代码审查被同事改成 === 就炸了。浮点数、字符串、严格比较,三杀。
现在我定了一套"迁移三件套"规矩
1. 版本号必须是字符串,且带日期/序号双保险
```php define( 'ZSENS_ADMIN_DB_VERSION', '20240831-003' ); // 永不歧义 ```拒绝纯数字、拒绝浮点。日期前缀还能一眼看出这版迁移是哪天的,排查时不用翻 git。
2. 升级函数必须幂等,dbDelta 只是起点
dbDelta 本身对字段变更有防护,但对数据迁移(比如拆表、改枚举值、重建索引)完全不设防。我的做法是升级函数内部再包一层版本分段:
这样重装时如果检测到 uninstalled- 前缀,就知道上次没卸干净,可以走修复流程而不是假装全新安装。
一个额外发现:dbDelta 的"沉默失败"
有次升级脚本里我把 KEY 写成了 INDEX(MySQL 支持,但 dbDelta 的解析器不认),dbDelta 返回空数组、没报错,表也建了,只是索引没加上。后续查询慢到超时,我以为是数据量问题,查了一下午执行计划才发现索引缺失。
现在我的升级函数必带 dbDelta 结果审计:
dbDelta 的返回值是个关联数组,Created table xxx 或 xxx already exists 都算正常,但如果出现 error 字样或者某张表完全没出现在返回值里,那就是有问题。
你们有没有遇到过更阴间的版本号坑?比如多站点环境下 get_blog_option 和 get_site_option 混用导致子站点版本号"夺舍"主站点?我下一个要攻的就是这个。