闭包通过封装数据库连接、变更逻辑与错误恢复流程,实现本地索引更新的原子化控制。它锁定上下文、统一调度步骤、分三阶段执行并适配多环境,最终暴露安全可控的更新接口。

闭包本身不执行索引操作,但它能封装数据库连接、变更逻辑与错误恢复流程,让本地索引更新具备“全成功才生效,任一失败即回退”的原子化控制能力。关键不在闭包有多“智能”,而在于它把上下文(如表结构、旧索引定义、临时状态)稳稳锁住,避免外部干扰。
用闭包封装索引变更上下文
本地数据库(如 SQLite、Capacitor SQLite 或 Electron 中的 better-sqlite3)通常不支持跨 DDL 语句的事务回滚(比如 CREATE INDEX 失败后自动 DROP 已建索引)。闭包可统一持有:当前连接实例、目标表名、待建/待删索引列表、以及一个用于记录中间状态的临时对象。这样所有步骤都在同一作用域内调度,不依赖全局变量或外部传参。
- 闭包初始化时读取当前索引快照(如通过 PRAGMA index_list(table)),存为内部 const oldIndexes
- 接收用户声明的变更意图,例如 { add: ['idx_users_email'], drop: ['idx_users_name'] }
- 所有 SQL 执行都复用同一个 db 实例,确保在单连接下顺序执行
实现“执行-验证-确认”三阶段原子流
真正的原子性不是靠数据库原生事务兜底,而是靠闭包内编排的显式控制流:先试运行、再校验结果、最后标记完成。失败时主动清理,而非依赖 abort()。
- 准备阶段:生成唯一变更 ID(如 `tx_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`),并写入元数据表(如 __schema_migrations)标记“pending”
- 执行阶段:按顺序执行 DROP INDEX / CREATE INDEX;每步成功后,将操作日志推入本地 undoStack(如 [() => db.exec('DROP INDEX idx_old')])
- 验证阶段:执行完全部语句后,再次查询 PRAGMA index_list,确认新索引存在、旧索引已删;任一不符即触发回滚
- 终态处理:验证通过则更新元数据表状态为 “applied”;失败则逆序调用 undoStack 并抛出带上下文的错误
适配不同本地存储的差异处理
不同环境对索引操作的支持粒度不同,闭包需按需注入适配逻辑,而不是写死 SQL:
- SQLite(WASM 或 Node.js):支持事务包裹 DDL,闭包内可用 BEGIN IMMEDIATE → 批量 exec → COMMIT/ROLLBACK,但需注意某些版本限制
- Capacitor SQLite 插件:execute() 不支持多语句事务,闭包应拆分为串行调用,并在 catch 中手动补全反向操作(如已建索引则 DROP)
- IndexedDB(模拟索引逻辑):无真实索引语法,闭包转为管理 objectStore 的 keyPath 与 indexNames 映射表,在 versionchange 事务中统一调整,失败时 revert 到上一版 schema
暴露安全可控的更新接口
闭包最终只导出一个函数,如 updateIndexes,使用者无需知道内部如何捕获异常或清理残留:
- 调用时只需传入变更描述对象,例如 updateIndexes({ add: ['posts_by_author'], drop: ['posts_by_status'] })
- 返回 Promise,resolve 时附带变更摘要(新增几个、删除几个、耗时多少)
- reject 时 Error 对象包含原始 SQL、失败位置、已执行的 undo 步骤数,便于定位和重试
- 不暴露 db 实例、undoStack 或 _config 等内部状态,防止误操作破坏原子边界

















