数据库与迁移
确认 D1 目标、检查实际结构和迁移记录,处理缺表、迁移中断与数据约束问题。
数据库问题先确认连接目标,再检查结构和数据。当前模板有两个 D1,业务数据在 DB,访问统计数据在 ANALYTICS_DB,本地和远程还分别拥有独立状态。
一、确认正在检查哪个数据库
| Binding | 内容 | 基线表 | 初始化文件 |
|---|---|---|---|
DB | 账号、支付、权限、工单、通知、Reaction 等 | saas_user | schema/db-init.sql |
ANALYTICS_DB | 访问统计会话、事件和属性 | analytics_session | schema/analytics-init.sql |
wrangler.jsonc 中的 binding 是代码访问名称,database_name 是资源名称,database_id 才是远程资源标识。CLI 会按项目名调整资源名称,不能始终寻找 saavo-template-db。
下面是本地只读检查,不会修改表结构:
npx wrangler d1 execute DB --local --command "SELECT name FROM sqlite_schema WHERE type = 'table' ORDER BY name;"
npx wrangler d1 execute ANALYTICS_DB --local --command "SELECT name FROM sqlite_schema WHERE type = 'table' ORDER BY name;"确认账户和资源 ID 后,远程查询明确使用 --remote:
npx wrangler d1 execute DB --remote --command "SELECT name FROM sqlite_schema WHERE type = 'table' ORDER BY name;"不要省略环境标志来猜测命令会操作哪里。命令参数见 D1 Wrangler 命令。
二、出现 no such table 或 no such column
先核对报错表属于哪个库,再判断是否存在遗漏迁移。
本地项目运行:
npm run db:migrate:local已经确认远程目标、迁移内容和兼容性的生产项目运行:
npm run db:migrate:remote这两个命令分别处理对应环境下的两个数据库,不是只处理 DB。脚本按顺序执行,前一个数据库成功、后一个失败时,不能认为两个库都没有变化。
缺少字段时,还应检查是否只修改了初始化 SQL。已有数据库不会因为基线文件增加一列就自动得到这一列,结构变化需要写入 schema/migrations。
用表结构确认结果,例如:
PRAGMA table_info('saas_user');查询结果符合代码需要后,再重试原业务请求。不要只以迁移命令没有报错作为全部验证。
三、非空数据库缺少基线表
迁移脚本的判断流程是:
- 没有用户表时,执行该库的初始化脚本。
- 已有用户表且存在对应基线表时,继续应用迁移。
- 已有用户表但缺少基线表时,停止执行。
第三种情况可能是绑定到了其他项目、导入不完整,或者之前执行过破坏性的结构操作。先保存表清单,核对资源 ID 和历史操作,再确定恢复方案。
saas_user 或 analytics_session 的存在只是脚本用于识别基线的条件,不是完整结构校验。不要手工创建一个空的同名表来绕过检查。
不要直接重跑初始化 SQL
基线文件含有删除旧表的语句。对已有数据的库执行初始化 SQL,或者执行 db:reset,会丢失数据。脚本没有撤销功能,恢复依赖预先准备的备份或数据库恢复能力。
四、迁移失败或部署中断
先记录失败数据库、迁移文件名、第一条 SQL 错误和执行时间。检查迁移目录中的命名:
schema/migrations/0001-db-add-example.sql
schema/migrations/0002-analytics-add-example.sql前缀必须是四位数字,中间的 db 或 analytics 决定归属。错误的文件名可能被检查脚本拒绝,或者没有进入对应库的迁移集合。
确认 d1_migrations 存在后,可在目标库只读查询:
SELECT id, name, applied_at
FROM d1_migrations
ORDER BY id DESC
LIMIT 20;同时检查实际表结构。已经完成的前序迁移不会因为后续失败而一起撤销,跨两个数据库的更新也不是一个整体事务。
| 检查结果 | 处理方向 |
|---|---|
| 迁移未执行,SQL 本身有错误 | 先在本地修复并验证,再应用到目标环境 |
| 文件已在部分环境执行成功 | 保留已发布迁移历史,通过后续迁移修正差异 |
| 迁移记录与结构不一致 | 检查是否有人手动改表或导入数据,不要直接删迁移记录 |
| SQL 已应用,但新 Worker 未发布 | 确认旧代码仍能运行,再修复发布步骤 |
当前 deploy:update 在发布新 Worker 前执行远程迁移。删除或重命名旧代码仍使用的列,可能在新版本发布前就造成线上故障,设计迁移时应同时考虑新旧版本。
五、查询不到刚刚保存的数据
依次检查:
- 写请求是否确实成功,响应体是否包含业务失败。
- 读取和写入是否使用同一个环境、同一个 Binding 和资源 ID。
- 是否需要经过 Webhook 或 Queue 才真正写入。
- 查询条件是否包含错误的用户 ID、连接 ID、状态或时间范围。
- 时间字段是否按 Unix 毫秒比较,避免把秒直接当成毫秒。
不要直接 SELECT * 导出会话或用户表排错。只查询本次需要的 ID、状态和时间,避免把 Token、哈希和加密资料混入共享日志。
六、唯一约束和外键约束失败
唯一约束失败既可能是重复请求,也可能是业务 Key 设计错误。支付和 Reaction 都有自己的幂等边界,应先定位违反了哪个索引,再追踪本次业务来源。
外键失败时,检查父记录是否存在、写入顺序是否正确,以及数据是否误写入另一个库。不要通过关闭外键检查让错误数据进入数据库。
特别是支付和权限数据,修复前要理解事件账本、交易明细、角色和权益之间的关系。手工删除一条记录可能让系统把已完成业务当作第一次执行,造成重复发放。
修复后的验证
在目标库确认表结构和迁移记录,重试一笔最小业务操作,再查询它对应的状态。涉及迁移时,还应确认已有用户能继续读取和更新旧数据。