SQLite 適合在 VPS 上正式使用嗎?
了解 SQLite 在單一 VPS 上正式運作的條件,包含 WAL、busy_timeout、Litestream 持續備份,以及單一寫入者與無法跨主機共用等限制。
SQLite 適合在 VPS 上作為正式環境資料庫的時機
對大多數小型應用程式而言,在 VPS 的正式環境中使用 SQLite 是正確選擇,原因很簡單:由一個程序在一台機器上將資料寫入一個檔案,不需要資料庫伺服器。不需要監督 daemon、不需要設定防火牆連接埠、不需要輪替密碼,也不需要維持第二台機器運作。查詢是函式呼叫,而不是網路往返,因此執行 40 次查詢的頁面只會產生 40 次函式呼叫。
這項取捨有明確且實際的限制。整個資料庫檔案同一時間只能有一個寫入者,而且檔案不能在兩台機器之間共用。對於在單一 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。修改頁面前,SQLite 會將原始頁面複製到 -journal 檔案,然後直接編輯資料庫。為了安全執行這項操作,SQLite 會鎖定整個檔案的獨佔鎖定,因此任何寫入進行時,所有讀取作業都必須等待。在筆記型電腦上通常不會察覺。在 Web 伺服器上,任何一次緩慢的寫入都會使所有存取該資料庫的要求停滯。
WAL(write-ahead log)模式會反轉這個順序。寫入者會將新頁面附加至獨立的 -wal 檔案,並保留主要資料庫不變。讀取者會依照開始讀取時建立的快照,持續讀取主要檔案,因此讀取者不會阻塞寫入者,寫入者也不會阻塞讀取者。之後,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 會儲存在資料庫中。以下其他設定都適用於個別連線。這表示應用程式必須在每個建立的連線上執行這些設定,包括連線集區在背景中建立的每個連線。
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;busy_timeout = 5000 會讓 SQLite 在資料庫遭鎖定時持續重試,最長 5000 毫秒,之後才回傳 database is locked。預設值為 0,因此兩個寫入者第一次重疊時,SQLite 預設會立即失敗。設定這個值即可消除大多數被歸咎於 SQLite 的鎖定錯誤。
synchronous = NORMAL 是 WAL 模式下適用的設定,但必須了解其中的取捨。設定為 FULL 時,SQLite 會在每次提交時對 WAL 呼叫 fsync。設定為 NORMAL 時,則會在檢查點處理期間同步。SQLite 文件明確說明了所放棄的特性:發生電源故障或硬重設後,交易不再具備持久性。電源中斷不會損毀資料庫,但尚未寫入磁碟的最後幾筆提交內容會遺失。對 VPS 而言,這通常是適當的取捨,因為每次寫入都不必執行 fsync。
foreign_keys = ON 預設為關閉,以維持向後相容性,而且適用於個別連線。即使結構描述中有許多 REFERENCES 子句,在每個連線啟用此設定之前,這些子句都不會強制執行任何條件。
還有一項設定只會在之後變得重要。WAL 成長超過 1000 個頁面後,SQLite 會自動執行檢查點;處理工作由當時剛好完成交易的連線執行。單獨使用時這沒有問題。但執行 Litestream 時,情況就需要考量,因為 Litestream 需要控制檢查點的執行時機。
設定 busy_timeout 後,為什麼 database is locked 仍會發生
這是讓使用者改回 Postgres 的錯誤,而且原因很明確。
busy timeout 會安裝 busy handler,但 SQLite 不保證一定會呼叫它。
如果 SQLite 判定呼叫 busy handler 可能導致死結,就會直接向應用程式回傳 SQLITE_BUSY,而不呼叫 busy handler。
它要避免的死結發生在交易升級時。SQLite 中單獨的 BEGIN 表示 BEGIN DEFERRED。如果其後的第一個陳述式是 SELECT,您就在讀取交易中。當同一個交易後續的 UPDATE 需要轉為寫入交易,而另一個連線自您開始讀取後已完成寫入時,SQLite 無法讓您等待。因為您的快照已經過期,等待只會讓兩個連線彼此形成死結。文件直接說明結果:
後續的寫入陳述式會在可能時將交易升級為寫入交易,否則回傳 SQLITE_BUSY。
您的 5000 毫秒逾時設定完全不會被查詢。錯誤會立即發生,因此看起來就像設定沒有作用。
修正方式只有一個詞。
BEGIN IMMEDIATE;
UPDATE notes SET body = 'edited' WHERE id = 1;
COMMIT;BEGIN IMMEDIATE 會在開始時取得寫入鎖定,先於任何讀取操作。這樣就不會發生升級,也沒有需要避免的死結,因此 busy handler 會生效,連線會等待輪到自己,而不是失敗。唯讀交易請維持 deferred。任何包含寫入操作的交易都應使用 immediate。
鎖定錯誤的第二個原因更難察覺:在緩慢工作期間持續保持寫入交易開啟。SQLite 會將寫入操作序列化,因此如果交易開啟後,透過網路呼叫外部 API,最後才提交,這段呼叫期間會阻塞所有其他寫入操作。先讀取所需資料,關閉交易,執行緩慢工作,然後開啟一個簡短的寫入交易來儲存結果。
使用 Litestream 進行持續備份
每晚複製一次最多會遺失 1 天的寫入內容,而對使用中的 SQLite 資料庫執行 cp,可能產生無法開啟的複本。有兩種方式是安全的。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截至 2026 年 7 月,官方 Linux 安裝頁面記載的版本是 v0.5.14,v0.5.15 已於 2026 年 7 月 21 日發布。請將兩行中的版本改為 releases page 上的目前 tag。如果 VPS 使用 arm64,請改用相符的 arm64 package。
設定檔位於 /etc/litestream.yml。請先使用本機檔案 replica,因為這樣不需要 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,現在包含兩個項目的設定會在啟動時失敗。許多 third-party guides 仍顯示舊的 array,因此請依照上述結構,不要直接採用搜尋結果中的第一個範例。0.5 series 也將 litestream wal subcommand 重新命名為 litestream ltx,因為磁碟上的 backup format 已變更。
啟用任何功能前,請確認設定檔可以解析。
sudo litestream databases -config /etc/litestream.yml接著手動驗證完整往返流程。這個形式會略過設定檔,將一個資料庫複製到一個路徑。
mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/app這會在 foreground 執行並持續運作。在第二個 shell 中寫入一列資料,然後將 replica 還原至新檔案。
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 秒的 sync-interval 推送,因此請稍候後再還原。這 1 秒也是你的 recovery point。系統當機時,最多會遺失上一個同步間隔內的寫入內容,沒有任何設定可以將此值降為 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 values 是預設值,而 retention 的預設值常令人意外。Retention 是 Litestream 保留 snapshots 及其相關檔案的時間,因此也決定你可以還原到多久以前。24 小時表示,如果你在星期三早上發現錯誤的 migration,便已無法從星期一的狀態復原。請將 retention: 168h 設為 1 週,並負擔額外的儲存空間費用。
在需要前先驗證還原
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 服務與計時器定期執行此操作,並讀取輸出。在至少成功還原一次備份前,您無法確認備份可正常運作。
在 systemd 下執行 Litestream
Debian 套件會安裝一個 litestream 單元,該單元會讀取 /etc/litestream.yml。
sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -f正常輸出會依序列出設定中的每個資料庫,之後除了定期同步訊息外不會持續輸出。若針對資料庫路徑出現 no such file or directory 錯誤,表示設定中的路徑錯誤,或程序無法讀取該路徑。此單元預設以 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 結束,因此每次開機執行都安全。請將它放在應用程式單元的 ExecStartPre 行中。如此一來,新建的 VPS 會下載資料庫,而現有的 VPS 則不會執行任何動作。litestream replicate 也提供相應的 -restore-if-db-not-exists 旗標,若您希望將設定集中管理,可以使用該旗標。
SQLite 在 VPS 上的限制
網路檔案系統。 這是無法透過設定繞過的限制。WAL 模式要求所有使用資料庫的程序共用一小段記憶體,而 -shm 檔案就是用來提供這段記憶體。SQLite 文件明確規定:
使用資料庫的所有程序都必須位於同一台主機電腦上;WAL 無法透過網路檔案系統運作。
因此,儲存在掛載的 NFS(網路檔案系統)或 SMB 共用上的資料庫可能損毀,且沒有任何 pragma 可以避免這個問題。這裡有一項常被忽略的區別。網路區塊裝置是多數 VPS 供應商附加額外儲存空間時使用的裝置。Linux 會將它視為具有一般檔案系統的普通磁碟,這樣沒有問題。掛載的檔案共用則不同。
第二台應用程式伺服器。 沒有任何設定可以讓這種架構正常運作。當你需要兩台機器提供相同資料時,就需要使用能透過網路通訊的資料庫。請在仍有時間規劃時決定是否採用這項架構變更。
大量寫入的工作負載。 一次只能有一個寫入者,是檔案格式的特性,不是可調整的設定。短時間寫入的成本很低,因為每次提交都會附加至 WAL。因此,吞吐量主要取決於磁碟的小型寫入延遲,而不是 CPU。請參閱 VPS 上的 NVMe 與 SATA SSD 儲存空間比較,瞭解兩者的差異。真正的問題是長時間交易,因為它們會讓其他寫入者全部排在後面。
分析查詢。 SQLite 是為交易處理設計的列式儲存資料庫。儀表板掃描一億筆資料是不同工具的不同工作,而 DuckDB 與 SQLite 的伺服器工作比較 說明了兩者的適用界線。
複製環境中的 VACUUM。 完整的 VACUUM 會重寫整個資料庫檔案。這表示 Litestream 必須再次上傳全部內容,而 Litestream 文件也建議不要在複製作業進行時直接執行此操作。請停止複製程序、執行 vacuum,再重新啟動複製程序,並預期會建立新的完整快照。
同一個資料庫上的兩個複製程序。 絕對不要讓兩個 Litestream 程序處理同一個資料庫或使用相同的複本目的地。文件明確指出,防止這種情況是你的責任;否則會產生無法還原的複本。
Litestream 不涵蓋的範圍
Litestream 只保護資料庫檔案,不處理其他內容。上傳的檔案、應用程式設定、TLS(傳輸層安全性)憑證和 unit 檔案仍需由您自行處理。請定期搭配 使用 restic 進行加密的異機備份,即可涵蓋這兩部分。如果這是新機器,新 VPS 的前 10 分鐘會處理本指南假設已完成的使用者帳戶和防火牆設定。
FAQ
SQLite 足以支援正式環境中的應用程式嗎?
對於一台伺服器上的單一應用程式,可以,但前提是啟用 WAL 模式、設定 busy timeout,並持續進行備份。重要的限制是結構性的:同一時間只能有一個寫入者,而且只能使用一台主機。符合這些限制的應用程式,可以使用不需經過網路傳輸、也不需監控獨立程序的資料庫。不符合這些限制的應用程式需要使用用戶端-伺服器資料庫,任何調校都無法改變這一點。
為什麼設定 busy_timeout 後仍會收到 database is locked?
因為等待可能造成死結時,SQLite 會略過 busy handler。以單獨的 BEGIN 開始的交易是延遲交易:開頭的 SELECT 會使其進入讀取交易,之後的寫入則必須升級交易。如果另一個連線在此期間完成寫入,SQLite 會立即傳回 SQLITE_BUSY,而不呼叫您的 busy handler,因為您的讀取快照已經過期。所有會執行寫入的交易都應以 BEGIN IMMEDIATE 開始,讓寫入鎖定一開始就取得,並使 timeout 生效。
可以將 SQLite 資料庫放在網路儲存裝置上嗎?
不能放在 NFS 或 SMB 等網路檔案系統上。WAL 模式需要所有程序透過 -shm 檔案共用記憶體,而且 SQLite 文件指出,所有使用該資料庫的程序都必須位於同一台主機電腦上。由供應商連接的網路區塊裝置則不同:Linux 會將其視為具有一般檔案系統的正常磁碟,SQLite 可以在其上運作。
如果已經執行每日備份,還需要 Litestream 嗎?
這取決於您能承受遺失多少資料。每日執行的工作代表最多可能遺失 24 小時的寫入內容。Litestream 約每秒同步一次,因此當機時大約只會遺失最後 1 秒的資料。使用 Litestream 也比使用 cp 複製資料庫檔案更安全,因為後者可能在資料庫寫入期間擷取檔案。Litestream 只涵蓋資料庫,因此仍應同時執行一般檔案備份。