SQLite Schema Migration and Versioning
SQLite schema migration 的核心責任是讓單檔資料庫隨 application release 安全演進。SQLite 沒有獨立 database server,也沒有 DBA 在 server 端統一套 migration;migration 常在 application startup、CLI command、mobile app upgrade 或 desktop app launch 時發生,因此 schema version、binary compatibility、backup 與 rollback 要放在同一個 release contract 中設計。
SQLite migration 同時改資料庫檔案與 application 能讀的資料格式,所以本文的範圍從 SQL 成功執行之後才開始:database file 怎麼記錄自己的 schema version、ALTER TABLE 做不到的變更怎麼用 table rebuild 完成、舊 binary 打開新 database 或新 binary 打開舊 database 時的相容策略,以及 migration 完成要留下哪些 evidence。
Version model
SQLite schema versioning 的服務責任是讓 application 能判斷 database file 目前處於哪個契約。SQLite 提供 PRAGMA user_version 作為 application-controlled integer;更複雜的服務也可以用 migration table 記錄多步驟版本、checksum 與執行時間。
1PRAGMA user_version;
2PRAGMA user_version = 2026052101;| 方式 | 適合情境 | 優點 | 邊界 |
|---|---|---|---|
user_version | mobile / desktop / CLI single file | 簡單、內建、開檔即可讀 | 只能存一個整數,缺 migration history |
| migration table | small backend、多人維護 schema | 可記錄每步 migration 與 owner | 需要先建立 table 與初始化流程 |
| external manifest | fixture、artifact、read-only DB | 可和 release artifact 綁定 | DB file 本身不含完整 history |
Version model 要在第一版就定義。沒有版本欄位的 SQLite file 仍可 migration,但 application 只能靠 introspection 猜 schema,會讓 upgrade / downgrade runbook 複雜化。
ALTER TABLE boundary
SQLite ALTER TABLE 的核心責任是處理有限集合的 schema 變更。官方文件說明 SQLite 支援 rename table、rename column、add column、drop column;更複雜的變更要走 table rebuild pattern。
| 變更類型 | SQLite 支援形態 | 操作判讀 |
|---|---|---|
| Rename table / column | 直接 ALTER,版本差異影響 trigger / view | 需要測 trigger、view、FK reference |
| Add column | 多數情境很快,受 default / constraint 限制 | 適合 expand migration |
| Drop column | 需要檢查 index、constraint、trigger、view | 可能掃資料,需 maintenance window |
| Change type / constraint | 通常走 table rebuild | 需要完整 copy、foreign key check、validation |
SQLite schema 存在 sqlite_schema 的 SQL text 中;這讓檔案格式簡潔,但也讓 ALTER TABLE 的安全條件和 server SQL 不同。Production migration 應優先用官方建議的 rebuild procedure,而非直接修改 sqlite_schema。
Table rebuild migration
Table rebuild migration 的服務責任是安全完成 SQLite 直接 ALTER 難以表達的變更。官方 ALTER TABLE 文件建議的 generalized procedure 是建立新 table、copy data、drop old、rename new、重建 index / trigger / view、跑 foreign key check、commit。
1-- 在交易之外關掉 FK:交易進行中執行 PRAGMA foreign_keys 不會生效
2PRAGMA foreign_keys = OFF;
3BEGIN;
4
5CREATE TABLE new_orders (
6 id INTEGER PRIMARY KEY,
7 status TEXT NOT NULL,
8 paid_at TEXT
9);
10
11INSERT INTO new_orders (id, status, paid_at)
12SELECT id, status, paid_at
13FROM orders;
14
15DROP TABLE orders;
16ALTER TABLE new_orders RENAME TO orders;
17
18-- 有輸出列就代表重建弄壞了 FK reference;SQL 腳本本身不會因此中止,
19-- 執行 migration 的程式要讀這個結果,有列就 ROLLBACK 而不是 COMMIT
20PRAGMA foreign_key_check;
21PRAGMA user_version = 2026052101;
22COMMIT;
23PRAGMA foreign_keys = ON;這段範例是教學骨架,而非可直接複製到所有 schema 的萬用腳本。真實 migration 要先保存 index、trigger、view 與 FK reference,再依 schema 重建;有資料量時還要考慮 copy duration、disk 空間與 rollback snapshot。
App release compatibility
SQLite migration 的 application compatibility 來自 binary 與 DB file 的同步問題。Server SQL migration 通常有 central deploy order;SQLite file 可能跟著使用者裝置、desktop profile、CLI artifact 或 edge deploy 留在不同版本。
| 相容性問題 | 真實情境 | 設計策略 |
|---|---|---|
| 新 app 打開舊 DB | 使用者升級 app | startup migration、read compatibility |
| 舊 app 打開新 DB | 使用者 downgrade、同步舊 binary | 保留 backward-compatible column、feature gate |
| 多裝置不同版本 | local-first / sync app | sync protocol version、server authority |
| fixture 與 production drift | test fixture 沒更新 | fixture version、contract test、migration smoke |
Compatibility 的核心是先決定支援範圍。Mobile app 常要支援舊版資料庫升級;internal CLI 可能只支援最新版本;test fixture 則需要每次 migration 後重新產生。
Migration evidence
Migration evidence 的責任是證明 schema 變更已完成且資料仍可用。SQLite migration evidence 比 server DB 簡單,但更依賴 application-level validation。
| Evidence | 目的 | 範例 |
|---|---|---|
| schema version | 確認 DB file 契約 | PRAGMA user_version |
| row count | 確認 copy / rebuild 無漏資料 | SELECT COUNT(*) FROM orders |
| domain query | 確認重要 business invariant | unpaid / paid 狀態數量 |
| foreign key check | 確認 reference integrity | PRAGMA foreign_key_check |
| integrity check | 檢查 DB 結構 | PRAGMA integrity_check |
| backup marker | 回退點 | pre-migration .backup file |
這些 evidence 應接到 Observability Evidence Package 或 release note。SQLite migration 失敗時,最清楚的 rollback 通常是回到 migration 前 snapshot,而非在同一檔案上繼續試錯。
Production 踩雷
Startup migration 把長時間 table rebuild 放進啟動路徑,讓 app 啟動卡住
Startup migration 的核心風險是把長時間 table rebuild 放在使用者啟動路徑。小表新增 column 可能很快;大表 rebuild、index 重建或 vacuum 類操作會讓 app 啟動、CLI command 或 API cold start 變慢。
修正方向是先估資料量。短 migration 可在 startup;長 migration 要有 explicit command、progress、backup 與 rollback route。
Fixture schema 與 production database 的 dialect 與 constraint 不一致
Fixture schema drift 的核心風險是測試 DB 和 production DB 的 dialect / constraint 不一致。SQLite fixture 很快,但 production 若是 PostgreSQL / MySQL,type、date、NULL、constraint 與 transaction 行為都可能不同。
修正方向是把 SQLite fixture 明確標成 contract test 層。Repository error mapping、domain invariant 可以用 SQLite;production-specific SQL 要用 production database container 驗證。
直接改 sqlite_schema 產生語法正確而語意破壞的 database file
直接改 sqlite_schema 的核心風險是產生語法正確但語意破壞的 database file。SQLite 官方文件提供 writable schema route,但同時強調錯誤修改可能讓 database corrupt / unreadable。
修正方向是讓 writable schema 成為最後手段。一般 migration 優先用 ALTER TABLE 或 table rebuild;需要特殊修復時先複製原檔,在副本驗證。
操作檢查清單
SQLite migration runbook 至少要記錄:
- DB file 目前
user_version與 application release version。 - Migration 是否可重入、是否可中斷後恢復。
- Migration 前 backup / snapshot 位置。
- 需要 table rebuild 的 table、資料量、index / trigger / view 清單。
- Validation query、row count、foreign key check、integrity check。
- 舊 binary / 新 binary 的相容策略。
- Fixture DB 是否已重新產生並被 contract test 使用。