Learning a few things about running SQLite

Julia Evans

關於運行 SQLite 學到的幾件事

哈囉!我最近在做一個 Django 網站,並決定使用 SQLite 作為資料庫。剛開始為網站使用 SQLite 時,我讀了一堆部落格文章,都說在小型網站的正式環境中使用 SQLite 完全沒問題,我也覺得確實沒問題,但我當時沒有充分意識到的是,SQLite 終究還是一個資料庫,資料庫是很複雜的,而我對於維運資料庫其實所知不多。

所以這裡分享我在運行 SQLite 過程中學到的幾個小心得。這是我第 4 個使用 SQLite 的網站,我覺得這次比較有挑戰性,因為有了 Django ORM 的強大功能,我讓資料庫做了比以前不用 Django 時更多的工作。

我一開始就像所有部落格文章建議的那樣,開啟了 WAL 模式,然後就抱著且戰且走的心態繼續下去。

ANALYZE 原來很重要

今天我在一個有 4000 筆資料的資料表上執行了一條查詢(使用SQLite 的 FTS5 來做全文檢索),結果花了 5 秒。這讓我覺得不太對勁:電腦應該是很快的啊!

結果發現,我需要的只是執行ANALYZE!有問題的查詢馬上就從 5 秒縮短到大約 0.05 秒(或是其他小到我懶得再去細究的數字)。我到現在還是不太確定查詢計畫到底哪裡出了問題,但我猜可能是某種意外呈二次方複雜度的狀況。

ANALYZE 會產生「統計資料」(我想是關於每個資料表有多少筆資料?以及其他一些資訊吧?),讓查詢規劃器能夠做出更好的選擇。

或許有一天我會學會怎麼看懂查詢計畫。

清理資料庫很棘手

偶爾我會遇到不小心在資料庫裡塞進一堆其實不需要的資料列的情況(例如來自django-tasks-db 已完成的任務),這時我就會想把它們清掉。

在這種情況下,我已經碰過好幾次的狀況是:

  1. 我執行某種指令來清理這些資料列
  2. 這個指令花了超過 5 秒,因為資料列很多(不過老實說,我對於為什麼這些 DELETE 陳述式會這麼慢還是有點疑問,也許是有很多 Python 程式碼在交易中執行,我不太確定)
  3. 就在這段期間,其中一個其他的 worker 嘗試寫入資料庫,並在 5 秒後逾時(我設定的逾時時間是 5 秒)
  4. 該 worker 因為無法寫入資料庫而當掉,VM 也跟著關閉

到目前為止,我的做法就是把這些清理操作分批小量執行,這樣就不需要執行超過 5 秒的資料庫查詢。不過,經歷這些之後,我也更能體會為什麼有人會想使用像 Postgres 這種「真正的」資料庫,它可以同時有多個寫入者。

或許以後當我需要做這類操作時,乾脆就讓網站暫時下線進行排程維護,不過我還沒想好要怎麼做的工作流程。

關於 ORM 查詢效能目前還沒有心得

到目前為止,我都是用 Django 的 ORM 隨心所欲地發出各種查詢,完全沒有在意查詢效能,除了 ANALYZE 那件事之外,大致上都還算順利。資料庫還很小(大概 10000 筆資料?),而且我預期它未來也會一直保持很小,所以希望這個做法可以繼續行得通。

備份 SQLite

我已經用過幾種方式來備份 SQLite。我想我其實還沒有真的測試過從備份還原,不過我通常會試著用 dead man's switch 來監控備份是否正常。

方式 1:restic

sqlite3 /data/calendar.db "VACUUM INTO '/tmp/calendar.sqlite'"
gzip /tmp/calendar.sqlite

# Upload backup to S3
# Sometimes the backup gets OOM killed and so it stays locked, do an unlock
restic -r s3://s3.amazonaws.com/some_bucket/ unlock
# Do the backup & prune old backups
restic -r s3://s3.amazonaws.com/some_bucket/ backup /tmp/calendar.sqlite.gz
restic -r s3://s3.amazonaws.com/some_bucket/ snapshots
restic -r s3://s3.amazonaws.com/some_bucket/ forget -l 1 -H 6 -d 2 -w 2 -m 2 -y 2
restic -r s3://s3.amazonaws.com/some_bucket/ prune

方式 2:litestream

最近我開始嘗試 Litestream,因為我覺得做增量備份可能會更有效率:我的 restic 備份有時會因為 OOM 被系統強制終止,讓我有點厭煩了。基本上我只要寫一個設定檔然後執行:

litestream replicate -config litestream.yml

我在設定檔裡設定了 retention: 400h,試圖保留一定程度的資料庫歷史紀錄,但我根本不知道這樣是否有用。

我一直都是備份到 AWS,這總是很麻煩,因為要在 AWS 主控台中操作來產生憑證實在很惱人。或許有一天我會改用其他相容於 S3 的替代方案。

你可以使用多個資料庫

我目前的專案只有一個資料庫,但我在Mess with DNS 上用過一個技巧,就是把資料表拆到三個不同的資料庫檔案中,因為我的資料表其實不需要放在同一個 db 裡。我覺得這樣還滿有幫助的。

Mess with DNS 已經在 SQLite 上運行了 4 年(從 2022 年開始),一直都很順利,我覺得從 Postgres 搬遷過來對那個專案來說是個很棒的選擇。

就是這樣!

看到自己要花多久時間才學會所用技術的一些基本知識,總是覺得有點有趣。我想我第一次在網頁專案中使用 SQLite 是在 2022 年,而我直到今天才知道 ANALYZE 的存在!我想再過一兩年,我又會學到其他某個非常基礎的功能吧。

一些參考資料

除了官方文件之外,我參考過的一些部落格文章:

原文由 Julia Evans 發布

本文章由 muse-spark-1.2-contributor 進行翻譯