SQLite 適合在 VPS 正式環境使用嗎?
了解 SQLite 在單一 VPS 上正式運作的適用範圍,並掌握 WAL、busy_timeout、Litestream 備份設定,以及單一 writer 與跨機器共用的限制。
在 VPS 上將 SQLite 作為正式環境資料庫的適用情境
對大多數小型應用程式而言,在 VPS 的正式環境中使用 SQLite 是正確的選擇,原因很簡單:單台機器上的單一程序將資料寫入單一檔案時,不需要資料庫伺服器。不必監督 daemon、不必設定連接埠的防火牆規則、不必輪替密碼,也不必維持第二台機器運作。查詢是函式呼叫,而不是網路往返,因此執行 40 個查詢的頁面只需進行 40 次函式呼叫。
代價明確,而且適用範圍有限。SQLite 在整個資料庫檔案上同一時間只能允許一個 writer,且檔案不能在兩台機器之間共用。對於在單一 VPS 上執行單一應用程式而言,這兩項限制都沒有問題。一旦超出這種架構,兩項限制都會立即成為致命問題。本指南涵蓋讓 SQLite 在伺服器上安全運作的設定、使用 Litestream 執行持續備份,以及應停止使用 SQLite 的時機。
先安裝命令列工具。以下操作均在 Ubuntu 24.04 上執行。
sudo apt update
sudo apt install -y sqlite3
sqlite3 --version該命令會輸出以 3. 開頭的版本資訊,後面接著建置日期與原始碼雜湊值。截至 2026 年 7 月,Ubuntu 24.04 提供 SQLite 3.45.1。你的應用程式可能不會使用這個二進位檔:多數語言執行環境會自行隨附 SQLite 函式庫的副本,而且通常是較新的版本。因此,在依賴近期功能之前,請先確認資料庫驅動程式回報的版本。
為什麼首先要變更 WAL 模式
SQLite 預設使用 rollback journal。變更頁面前,它會將原始頁面複製到 -journal 檔案,再直接修改資料庫。為了安全執行這項操作,SQLite 會鎖定整個檔案的獨佔鎖定。因此,只要有寫入進行中,所有讀取操作都必須等待。在筆記型電腦上通常不會察覺。在 Web 伺服器上,任何寫入速度緩慢的操作,都會讓所有存取該資料庫的請求停滯。
WAL(write-ahead log)模式會反轉這個順序。寫入者會將新頁面附加到獨立的 -wal 檔案,主資料庫則保持不變。讀取者會依開始讀取時建立的 snapshot,持續讀取主檔案。因此,讀取者不會阻塞寫入者,寫入者也不會阻塞讀取者。之後,checkpoint 會將累積的 WAL 頁面複製回主資料庫。這項變更是讓 SQLite 能在 Web 應用程式後端穩定運作的主要原因。
啟用 WAL 模式並確認設定已持續生效
mkdir -p ~/app
sqlite3 ~/app/app.db "PRAGMA journal_mode=WAL;"指令會輸出 wal。這項輸出不是裝飾資訊。PRAGMA journal_mode 會回傳資料庫實際使用的模式,因此若回應為 delete,表示變更失敗,資料庫仍在使用 rollback journal。
WAL 模式具有持久性。它是資料庫標頭中的旗標,而不是連線設定。因此,每個資料庫檔案只需執行一次,之後的所有連線都會繼承此設定,重新開機後也一樣。請使用新的連線加以確認。
sqlite3 ~/app/app.db "PRAGMA journal_mode;"現在建立資料表,並查看磁碟上出現的檔案。
sqlite3 ~/app/app.db <<'SQL'
CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL);
INSERT INTO notes (body) VALUES ('first row');
SQL
ls -l ~/app/現在共有 3 個檔案:app.db、app.db-wal 和 app.db-shm。-wal 檔案儲存尚未完成 checkpoint 的已提交頁面。-shm 檔案是共享記憶體索引,所有連線都會對應該檔案,以便對 WAL 的內容保持一致。這兩個檔案都屬於資料庫,不是暫存檔案。應用程式執行期間若只複製 app.db,取得的檔案會遺漏所有最近的提交。若刪除 app.db 而保留另外兩個檔案,SQLite 會將那些過期的 WAL 頁面套用至以該名稱建立的新檔案。這就是嘗試重設資料庫時,可能導致全新資料庫損毀的原因。
生產環境應用程式需要的連線設定
只有 journal_mode 會儲存在資料庫中。以下其他設定都以連線為單位,表示應用程式必須在每個開啟的連線上執行這些設定,包括 connection pool 在背景建立的每個連線。
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;busy_timeout = 5000 會讓 SQLite 在回傳 database is locked 前,持續重試遭鎖定的資料庫,最長可達 5000 毫秒。預設值為 0,因此 SQLite 預設會在兩個寫入者第一次重疊時立即失敗。只設定這個值,就能排除大多數通常歸咎於 SQLite 的鎖定錯誤。
synchronous = NORMAL 是 WAL mode 中正確的設定,但其中的取捨值得了解。在 FULL 下,SQLite 每次 commit 都會對 WAL 呼叫 fsync。在 NORMAL 下,SQLite 會改在 checkpoint 時同步資料。SQLite 文件明確說明了因此放棄的特性:發生電源故障或硬重設後,交易不再具備 durability。資料庫不會因為斷電而損毀,但尚未寫入磁碟的最後幾筆 commit 會遺失。對 VPS 而言,這通常是合理的取捨,因為每次寫入都不必執行一次 fsync。
foreign_keys = ON 預設為關閉,以維持向後相容性,而且設定以連線為單位。即使 schema 中充滿 REFERENCES 子句,在每個連線啟用此設定前,也完全不會強制執行任何條件。
另一個設定只有在之後才會變得重要。WAL 大小超過 1000 個 pages 後,SQLite 會自動執行 checkpoint,而這項工作會由當下恰好完成交易的連線執行。單獨使用時這沒有問題。但在執行 Litestream 時,這就會成為需要考量的問題,因為 Litestream 需要控制 checkpoint 的執行時機。
設定 busy_timeout 後為何 database is locked 仍會發生
這是讓使用者改回 Postgres 的錯誤,而且原因很明確。
busy timeout 會安裝 busy handler,但 SQLite 不保證一定會呼叫它。
如果 SQLite 判定呼叫 busy handler 可能造成 deadlock,就會直接將 SQLITE_BUSY 傳回應用程式,而不呼叫 busy handler。
它要避免的 deadlock 發生在 transaction 升級時。在 SQLite 中,單獨的 BEGIN 表示 BEGIN DEFERRED。如果它後面的第一個 statement 是 SELECT,目前就是 read transaction。稍後同一個 transaction 中的 UPDATE 需要將 transaction 升級為 write transaction 時,如果另一個 connection 自 read transaction 開始後已經寫入資料,SQLite 就無法讓目前 connection 等待。這是因為目前的 snapshot 已經過期,繼續等待只會讓兩個 connection 彼此 deadlock。文件直接說明了結果:
後續的 write statement 會在可能的情況下將 transaction 升級為 write transaction,否則傳回 SQLITE_BUSY。
你的 5000 毫秒 timeout 根本不會被使用。錯誤會立即傳回,因此看起來就像設定沒有生效。
修正方式只有一個詞。
BEGIN IMMEDIATE;
UPDATE notes SET body = 'edited' WHERE id = 1;
COMMIT;BEGIN IMMEDIATE 會在一開始、讀取任何資料之前取得 write lock。這樣就不會發生升級,也沒有需要避免的 deadlock,因此 busy handler 會生效,connection 會等待輪到自己,而不是直接失敗。僅包含讀取操作的 transaction 應維持 deferred。任何包含寫入操作的 transaction 都應使用 immediate。
lock 錯誤的第二個原因更難察覺:讓 write transaction 跨越緩慢操作而長時間保持開啟。SQLite 會序列化 writers,因此如果 transaction 開啟後,透過 network 呼叫外部 API,最後才提交,這段呼叫的時間內會阻塞所有其他 writer。先讀取所需資料並關閉 transaction,執行緩慢操作,然後再開啟一個簡短的 write transaction 儲存結果。
使用 Litestream 進行持續備份
每晚複製一次最多會遺失 1 天的寫入內容,而對使用中的 SQLite 資料庫執行 cp,可能產生無法開啟的複本。有 2 種安全做法。sqlite3 app.db ".backup /path/to/backup.db" 使用 SQLite 的線上備份介面,可對使用中的資料庫執行。Litestream 更進一步監看 WAL,持續將變更傳送至物件儲存,將最壞情況下的資料遺失時間從 1 天降低至約 1 秒。
Litestream 是與應用程式並行執行的單一 Go binary。它不會位於應用程式與資料庫之間。應用程式照常寫入 SQLite,Litestream 則讀取 WAL 並上傳變更內容。
cd /tmp
curl -fsSL -O https://github.com/benbjohnson/litestream/releases/download/v0.5.14/litestream-0.5.14-linux-x86_64.deb
sudo dpkg -i litestream-0.5.14-linux-x86_64.deb
litestream version截至 July 2026,官方 Linux 安裝頁面所記載的版本是 v0.5.14,而 v0.5.15 已於 21 July 2026 發布。請將兩行中的版本改為 releases page 上的目前 tag;如果 VPS 使用 arm64,請改用相符的 arm64 套件。
設定檔位於 /etc/litestream.yml。先使用本機檔案複本,因為這樣不需要 cloud credentials,就能驗證整個流程。
dbs:
- path: /home/appuser/app/app.db
replica:
type: file
path: /var/backups/litestream/app請注意,欄位是單數形式的 replica。Litestream 0.5 將 0.3 series 的 replicas array 改為單一 replica block;現在若設定檔包含 2 個項目,啟動時會失敗。許多 third-party guides 仍顯示舊的 array,因此請依照上面的結構複製,不要直接採用搜尋結果中的第一個範例。0.5 series 也將 litestream wal subcommand 重新命名為 litestream ltx,因為磁碟上的備份格式已變更。
啟用任何功能前,先確認設定檔可以正確解析。
sudo litestream databases -config /etc/litestream.yml接著手動驗證完整流程。此形式會略過設定檔,將 1 個資料庫複製到 1 個路徑。
mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/app這會在前景執行並持續運作。在第 2 個 shell 中寫入 1 列資料,然後將複本還原到新檔案。
sqlite3 ~/app/app.db "INSERT INTO notes (body) VALUES ('written after replication started');"
litestream restore -o /tmp/restored.db file:///tmp/replica/app
sqlite3 /tmp/restored.db "SELECT count(*) FROM notes;"計數包含新增的資料列。如果不包含,表示變更尚未同步:Litestream 會依照預設為 1 second 的 sync-interval 推送內容,因此請稍候再還原。這 1 second 也是你的復原點。系統當機時,最多會遺失上次同步間隔內的寫入內容,任何設定都無法將這個時間降為 0。
正式使用時,將 replica block 改為 S3 URL。這可用於 Amazon S3,也可用於其他供應商提供的 S3-compatible object storage。
dbs:
- path: /home/appuser/app/app.db
replica:
url: s3://your-bucket-name/app
region: us-east-1
snapshot:
interval: 24h
retention: 24h請勿將 credentials 寫入該檔案。Litestream 會從環境讀取 LITESTREAM_ACCESS_KEY_ID 與 LITESTREAM_SECRET_ACCESS_KEY,因此請將它們放入由 root 擁有且 mode 為 600 的 systemd drop-in。
上面的 snapshot 值是預設值,而 retention 的預設值常讓人意外。Retention 指 Litestream 保留 snapshots 及其所屬檔案的時間,因此也決定可還原到多早以前。Twenty-four hours 表示你在 Wednesday morning 發現錯誤 migration 時,已無法從 Monday's state 還原。請將 retention: 168h 設為 1 week,並負擔額外的 storage 成本。
在需要前先驗證還原
litestream restore -o /tmp/check.db /home/appuser/app/app.db
sqlite3 /tmp/check.db "PRAGMA integrity_check;"
sqlite3 /tmp/check.db "SELECT count(*) FROM notes;"給定資料庫路徑後,litestream restore 會在 /etc/litestream.yml 中查找相符的複本並下載。對於狀態正常的檔案,PRAGMA integrity_check 會輸出 ok;任何其他輸出都表示還原的複本無法使用。使用 systemd service 與 timer 排程執行此作業,並讀取輸出。在實際還原過一次備份前,無法確認備份是否可用。
在 systemd 下執行 Litestream
Debian 套件會安裝 litestream unit,該 unit 會讀取 /etc/litestream.yml。
sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -f正常輸出會依設定列出每個資料庫,之後除了定期同步訊息外不會再輸出其他內容。若針對資料庫路徑出現 no such file or directory 錯誤,表示設定中的路徑錯誤,或程序無法讀取該路徑。此 unit 預設以 root 執行,權限高於這項工作所需。Litestream 必須能讀取及寫入資料庫與存放資料庫的目錄,因為它會處理資料庫旁的 -wal 與 -shm 檔案。因此,請改用應用程式目前使用的帳號。
# /etc/systemd/system/litestream.service.d/override.conf
[Service]
User=appuser
Group=appuser使用 sudo systemctl daemon-reload 與 sudo systemctl restart litestream 套用設定。建立具備最小權限的專用服務帳號只需幾分鐘,卻能讓系統上的備份代理程式與另一個以 root 執行的程序有所區別。
如果日後需要從零重建機器,還有一項啟動順序很重要。您必須先還原資料庫,再啟動應用程式。litestream restore 接受 -if-db-not-exists;檔案已存在時會以狀態碼 0 結束,因此每次開機執行都很安全。請將它放在應用程式 unit 的 ExecStartPre 行中,這樣新的 VPS 會在應用程式啟動前下載資料庫,現有的 VPS 則不會執行任何動作。若要將設定集中在同一處,litestream replicate 也提供相應的 -restore-if-db-not-exists 旗標。
VPS 上 SQLite 失效的情況
網路檔案系統。 這是無法透過設定避開的限制。WAL 模式要求使用資料庫的每個程序共用一小段記憶體,而 -shm 檔案就是用來提供這段共用區域。SQLite 文件明確說明這項規則:
使用資料庫的所有程序都必須位於同一台主機電腦上;WAL 無法透過網路檔案系統運作。
因此,儲存在掛載的 NFS(network file system)或 SMB share 上的資料庫可能損毀,沒有任何 pragma 能避免這個問題。這裡有一項常被忽略的區別。網路 block 裝置是多數 VPS 提供商用來附加額外儲存空間的方式;對 Linux 而言,它會呈現為具有一般檔案系統的普通磁碟,這沒有問題。掛載的 file share 則不同。
第二台應用程式伺服器。 沒有任何設定能讓這種架構正常運作。當你需要由兩台機器提供相同資料時,就需要使用能透過網路通訊的資料庫。請在還有時間規劃時決定是否進行這項遷移。
大量寫入的工作負載。 同一時間只能有一個 writer,這是檔案格式的特性,不是可調整的參數。短寫入的成本很低,因為每次 commit 都是附加到 WAL,因此吞吐量主要取決於磁碟的小型寫入延遲,而不是 CPU。請參閱 VPS 上的 NVMe 與 SATA SSD 儲存裝置比較,了解兩者的差異。真正的問題是長交易,因為它們會讓其他 writer 全部排隊等待。
分析查詢。 SQLite 是為交易處理設計的 row store。儀表板掃描一億列資料,是不同工具應處理的不同工作;DuckDB 與 SQLite 的伺服器工作比較說明了兩者的適用界線。
複製期間的 VACUUM。 完整的 VACUUM 會重寫整個資料庫檔案,因此 Litestream 必須再次上傳完整檔案;Litestream 文件也建議不要在複製作用期間直接執行這項操作。請停止 replicator、執行 vacuum,再重新啟動 replicator,並預期會產生新的完整 snapshot。
同一個資料庫上執行兩個 replicator。 絕對不要讓兩個 Litestream 程序處理同一個資料庫或相同的 replica destination。文件明確指出,避免這種情況是你的責任;否則產生的 replica 將無法還原。
Litestream 不涵蓋的範圍
Litestream 只保護資料庫檔案,不處理其他內容。上傳的檔案、應用程式設定、TLS(傳輸層安全性)憑證及 unit 檔案仍需由您自行管理。搭配排程執行的使用 restic 進行加密的異機備份,即可涵蓋這兩個部分。如果這是新主機,新 VPS 上線後的前 10 分鐘會說明本指南假設已完成的使用者帳號與防火牆設定。
FAQ
SQLite 適合正式環境的應用程式嗎?
如果是在一台伺服器上執行一個應用程式,SQLite 足夠使用,但前提是啟用 WAL 模式、設定 busy timeout,並持續進行備份。真正重要的限制是架構上的:同一時間只能有一個寫入者,而且只能使用一台主機。符合這些限制的應用程式,可以使用不需經過網路、也不需要另外監控獨立程序的資料庫。不符合這些限制的應用程式需要 client-server 資料庫,任何調校都無法改變這項需求。
為什麼設定 busy_timeout 後,仍然會遇到 database is locked?
因為等待可能造成 deadlock 時,SQLite 會略過 busy handler。以單獨的 BEGIN 開始的 transaction 是 deferred transaction:開頭的 SELECT 會讓它進入 read transaction,之後的寫入則必須升級。如果另一個 connection 在此期間完成寫入,SQLite 會立即回傳 SQLITE_BUSY,而不呼叫你的 busy handler,因為你的 read snapshot 已經過時。凡是會執行寫入的 transaction,都應使用 BEGIN IMMEDIATE 開始,讓寫入鎖定在一開始就取得,並套用 timeout。
可以將 SQLite 資料庫放在網路儲存設備上嗎?
不可以放在 NFS 或 SMB 等 network filesystem 上。WAL 模式需要所有程序透過 -shm 檔案共用記憶體,而 SQLite 文件明確指出,所有使用該資料庫的程序都必須位於同一台主機。由供應商連接的 network block device 則不同:Linux 會將它視為具有一般 filesystem 的一般磁碟,SQLite 可在其上正常運作。
如果已經執行每晚備份,還需要 Litestream 嗎?
這取決於你能承受遺失多少資料。每晚執行一次的工作,代表最多可能遺失 24 小時的寫入內容。Litestream 約每秒同步一次,因此當機時大約只會遺失最後 1 秒的資料。使用 Litestream 也比透過 cp 複製資料庫檔案安全,因為後者可能在資料庫寫入期間擷取檔案。Litestream 只涵蓋資料庫,因此仍應同時執行一般檔案備份。