MOOSSin
選單

全部文章 · 開源工具

SQLite:一個檔案就是一整個資料庫,它適合什麼、不適合什麼

世界上部署最多的資料庫引擎沒有伺服器程序,只是一個函式庫和一個檔案。這篇談它的運作方式、WAL 模式、正確的備份方法,還有我認為該換掉它的那條線在哪裡,順便聊聊為什麼它像書桌抽屜裡的零食格。

哈囉,今天想聊一個很多人每天都在用、卻幾乎沒有人注意過它的東西。你手機裡的通訊軟體、瀏覽器的書籤、某些遊戲的存檔,背後很可能都是同一個資料庫引擎在默默工作。它叫 SQLite,名字聽起來像某種輕量版的什麼,實際上它是全世界部署數量最多的資料庫引擎,沒有之一。

先講一個小場景。期中考週的某個晚上,朋友在群組裡哀號,說她的專題要「架資料庫」,光是看安裝教學就想放棄,什麼帳號密碼、什麼埠號、什麼連線池,每一個名詞都像在對她說「你不屬於這裡」。我回她:你需要的搞不好只是一個檔案。她以為我在講幹話。

一個檔案,真的就是一個檔案

SQLite 是一個 C 語言寫的資料庫函式庫,2000 年由 D. Richard Hipp 發表,程式碼放在公共領域,連授權條款都沒有,你想拿它做什麼都行。它不跑伺服器。你的程式直接把它連結進來,資料庫就是磁碟上的一個檔案,副檔名叫什麼都可以。沒有伺服器程序表示沒有埠、沒有帳號密碼、沒有連線池,備份和搬移在概念上就是複製一個檔案。

用便利商店來比喻的話,一般的資料庫伺服器像是一間有店員、有排班表、有收銀系統的店,你要買東西得先進店、排隊、結帳;SQLite 像是你書桌抽屜裡放零食的那一格,伸手就拿,沒有人會問你會員卡。抽屜當然裝不下一整間店的貨,但如果你只是想在半夜趕報告的時候有洋芋片可以吃,抽屜是對的答案。

Python 的標準函式庫內建 sqlite3 模組,不用裝任何東西就能用。朋友那天晚上照著我丟給她的三行程式試了一下,回我一句「就這樣?」。對,就這樣。程式和資料庫在同一個行程裡,一次查詢的成本是一次函式呼叫,不是一次網路往返。對讀多寫少、資料量在幾十 GB 以下的應用,這個模型很難被打敗。

我自己的網站也放在它上面

跟人說起我的網站資料庫是 SQLite,對方常會客氣地問一句「流量大了怎麼辦」。語氣有點像在問「你確定要穿這樣出門嗎」。我的回答通常是:先弄清楚它的邊界在哪,再決定要不要擔心。大部分人擔心的那個「流量大了」,其實還很遠很遠。

WAL 模式與三個檔案

預設的 journal 模式是 DELETE,寫入時會鎖住整個資料庫,其他人只能排隊。開啟 WAL 模式後,寫入先追加到一個 -wal 檔,讀取者不會被寫入者擋住,另外還有一個 -shm 共享記憶體檔。三個檔案是一組,這件事在備份時很重要。

我第一次看到資料夾裡憑空多出兩個檔案的時候,還以為是什麼病毒,差點手動把它們刪掉。後來才知道那是 SQLite 的日記本和便條紙,刪了它們就等於把還沒謄進正本的筆記撕掉。

備份不是複製檔案

有沒有過這種經驗:報告快寫完了,很聰明地把檔案拷到隨身碟備份,結果隔天打開,隨身碟那份是壞的,因為拷的時候檔案還開著。SQLite 的 WAL 模式也會發生一模一樣的事。正在使用中的資料庫,直接複製主檔會漏掉還在 -wal 裡的內容,連三個檔案一起複製也可能拿到互相對不上的組合,平常打得開,真正需要它的那天才發現壞了。

正確的做法有兩個:線上備份 API,它在一致的狀態下把資料庫複製到另一個檔案;或者 VACUUM INTO,用一句 SQL 產生一份緊實的副本。兩者都不需要停止服務,也不用把使用者趕出去。

有一個細節值得知道:透過 Python 的 Connection.backup() 做出來的副本會保留來源的 journal 模式,WAL 來源就會產出 WAL 副本,旁邊又多出 -wal 和 -shm。如果你的備份驗證只認一個檔案,記得把副本改回 DELETE 模式再驗,不然你的驗證程式會拒絕自己做出來的東西,然後你會對著螢幕發呆五分鐘,問自己到底哪裡做錯了。我發呆過。

只讀的時候就只讀

連線字串支援 URI 形式,file:app.db?mode=ro 開出來的連線寫不進去。任何只做讀取的工具都該這樣開。這有點像去朋友家借書,進門前先把筆放在門口,這樣你就算再手癢也不會在人家的書上畫線。這是防止「不小心寫到正式資料」最便宜的一道保險,一個參數的事。

小劇場:它到底適不適合我

朋友後來又來問,這次是幫另一個組的同學問的。

「所以我們的專題可以用它?」

「你們的專題有幾個人會同時寫資料?」

「就我們一個人在維護,其他人只是打開網頁看。」

「那可以,而且你們會愛上它。」

「那什麼時候不可以?」

「同一時間有很多人搶著寫的時候。像選課系統,開放的那一秒幾千個人同時按下去,那種東西不要用它。」

「所以它比較像圖書館的自習室,大家安靜坐著看書,偶爾一個人起來借書;不像演唱會售票,所有人同時衝進去。」

「這個比喻可以,我要偷走。」

它不擅長的事

同一時間只能有一個寫入者。多個行程同時寫,其他的會等鎖,等太久就收到 database is locked。所以我的規矩是每個資料庫只留一條寫入路徑,誰要寫都排隊走那扇門。網路檔案系統上的 SQLite 也不可靠,鎖定行為在 NFS 和 SMB 上都不保證,把資料庫檔放在共用資料夾裡然後多台電腦一起用,那是在跟機率賭博。

還有一個 Windows 特有的小事:只要還有連線沒關,檔案就會被鎖住,刪不掉也搬不動。測試清理前關掉所有連線,不然會看到一堆莫名其妙的權限錯誤,你會以為是防毒軟體在搞你,其實是你自己十行前開的那條連線忘了關。

什麼時候該換

我的線很簡單:出現第二個需要同時寫入的程序那天。流量本身通常不是理由,讀取可以靠 WAL 和快取撐很久。如果你的應用有很多來源同時寫入,像是持續收進來的資料流,一開始就用 PostgreSQL 會省事得多。內容型的網站,先用 SQLite,把力氣花在備份和寫入路徑上。

最後聊一下

我很喜歡 SQLite 的一點是它的文件。官方網站看起來像十五年沒改版,字很小,配色很復古,但裡面每一個奇怪的行為都有一頁解釋,而且是作者本人寫的,語氣像一個很有耐心的老師。有一頁專門在講「什麼時候不要用 SQLite」,一個產品願意自己寫這種頁面,我覺得很可以信任。

朋友的專題後來拿了不錯的分數。評審問她資料庫用什麼,她說 SQLite,評審點點頭說了一句「務實」。她跟我說那一刻她覺得自己好像懂了什麼。我說沒有,你只是少受了一次苦,而少受苦這件事,在寫程式的世界裡就已經是懂了什麼。

幾個可以拿去炫耀的冷知識

SQLite 的正確唸法,作者本人說是「S-Q-L-ite」,像礦物的名字,不過唸成「sequel-lite」他也不會生氣,他在訪談裡說過怎麼唸都行。這種不在意的態度跟整個專案的氣質很像。

它的測試程式碼比函式庫本身多幾百倍。你沒看錯,是幾百倍。函式庫十幾萬行,測試上億行的量級,涵蓋每一條分支、每一種記憶體不足的情況、每一種磁碟寫到一半斷電的情況。這是為什麼它可以被放進飛機、手機、汽車裡而沒有人擔心。下次有人跟你說「測試寫太多很浪費時間」,你可以請他去看一下 SQLite 的測試策略那一頁,然後看他的表情。

因為程式碼在公共領域,理論上沒有人擁有它,也就沒有人能「授權」給你。有些公司的法務部門無法接受「沒有授權書」這件事,SQLite 團隊於是提供一種付費的「所有權保證書」,內容大致是「我們保證這真的是公共領域的,你可以放心」。花錢買一張紙證明東西是免費的,這大概是軟體業最哲學的商品之一。

它被用在多少地方?作者的估計是每台安卓手機、每台 iPhone、每個主流瀏覽器、每台 Mac 和 Windows 10 之後的電腦裡都有至少一份,大部分有好幾份。你現在用來看這篇文章的裝置裡,很可能就有幾十個 SQLite 資料庫在安靜地工作。

常見的三個誤會

第一個誤會是「它是玩具,正式專案不能用」。上面那些飛機和手機大概不同意。它不適合的是特定的使用模式,不是特定的規模。

第二個誤會是「它很慢」。單一寫入者、本機檔案、沒有網路往返,很多簡單查詢它比伺服器型資料庫快,因為少了整層網路。它慢的地方是很多人同時寫,那是設計取捨,不是效能問題。

第三個誤會是「它不支援什麼什麼」。交易、外鍵、索引、視圖、觸發器、視窗函式、JSON 函式、全文檢索,該有的它都有。少的是使用者權限管理,因為一個檔案本來就沒有「使用者」這個概念,你能開那個檔案你就是全部的權限。

一個週末可以試的小練習

如果你從來沒碰過它,這裡有一個一小時內做得完的練習,不需要裝任何東西,只要電腦上有 Python。

開一個終端機,進入 Python,import sqlite3,sqlite3.connect("練習.db"),你就有了一個資料庫。建一張表放你這學期的課,欄位是課名、星期幾、教室。塞幾筆進去,查一下星期三有什麼課。然後執行 PRAGMA journal_mode=WAL,再去看資料夾,你會看到那兩個新朋友 -wal 和 -shm。最後用 VACUUM INTO '備份.db' 做一份備份,打開備份確認資料都在。

做完你會發現,「架資料庫」這四個字在很多情境下其實是「建一個檔案」的意思。朋友那天晚上的「就這樣?」,你也會說一次。

後記

寫這篇的時候我一直在想,為什麼一個這麼常見的東西這麼少人認真介紹它。大概是因為它太不會出事了,不出事的東西沒有新聞。這篇文章算是我替它做的一點公關,雖然它完全不需要。

官方網站:sqlite.org

閱讀偏好

返回全部文章作者與內容更正

留言(0)

還沒有留言。讀完有想法,歡迎留下第一則。

留言經站長審核後才會顯示。暱稱和留言會公開,請不要寫下電話、地址等個人資料;資料怎麼處理,請見隱私權政策。