小巧、快速、可靠
任選三項。
SQLite 的運作原理

1. 背景

SQLite 是一個軟體函式庫,它會將應用程式產生的高階磁碟 I/O 要求轉換為低階 I/O 作業,而作業系統可以執行這些作業。應用程式使用SQL 語言建構高階 I/O 要求。SQLite 會將每個高階 SQL 陳述式轉換為許多低階 I/O 要求的順序(開啟檔案、從檔案讀取幾個位元組、寫入幾個位元組到檔案等),以執行 SQL 要求的工作。

應用程式程式可以透過直接呼叫作業系統 I/O 常式或使用像Berkeley DBRocksDB(僅舉兩個)這樣的鍵值儲存引擎來執行所有磁碟 I/O。但使用基於 SQL 語言的高階介面有其優點。

  1. SQL 是一種非常高階的語言。幾行 SQL 可以取代數百或數千行程序碼。因此,SQL 減少了開發和維護應用程式所需的工作量,進而有助於減少應用程式中的錯誤數量。

  2. SQL 和 SQLite 是 交易性 的。使用交易性儲存系統,可以更輕鬆地推論應用程式的行為,並撰寫健全的應用程式,即使面對軟體錯誤、硬體故障或斷電。

  3. SQLite 通常 比直接的低階 I/O 更快。這是不直覺的。人們會預期像 SQLite 這種高階介面會造成執行時間的損失。而且理論上來說,這也是正確的。但實際上,像 SQLite 這種基於 SQL 的系統會執行許多幕後的最佳化,而應用程式開發人員永遠沒有時間建立和維護這些最佳化,因此基於 SQL 的系統最後會提供淨效能收益。

1.1. SQLite 與大多數其他 SQL 資料庫不同

除了 SQLite 之外,還有許多基於 SQL 的資料庫管理系統可用。常見的選項包括 MySQL、PostgreSQL 和 SQL-Server。這些系統都使用 SQL 語言與應用程式溝通,就像 SQLite 一樣。但這些其他系統在重要方面與 SQLite 不同。

  1. SQLite 是一個無伺服器軟體函式庫,而其他系統是基於客戶端伺服器。使用 MySQL、PostgreSQL、SQL-Server 等,應用程式會傳送包含一些 SQL 的訊息到一個獨立的伺服器執行緒或程序。那個獨立的執行緒或程序會執行要求的 I/O,然後將結果傳送回應用程式。但 SQLite 沒有獨立的執行緒或程序。SQLite 在與應用程式相同的地址空間中執行,使用相同的程式計數器和堆積儲存。SQLite 不進行任何進程間通訊 (IPC)。當應用程式將 SQL 陳述式傳送至 SQLite(透過呼叫適當的 SQLite 函式庫子常式)時,SQLite 會在與呼叫者相同的執行緒中詮釋 SQL。當 SQLite API 常式傳回時,它不會留下任何與應用程式分開執行的背景工作。

  2. SQLite 資料庫是磁碟上的單一普通檔案(具有定義良好的檔案格式)。使用其他系統時,「資料庫」通常是隱藏在檔案系統的模糊目錄中或甚至分散在多部機器上的大量獨立檔案。但使用 SQLite 時,完整的資料庫只是一個普通的磁碟檔案。

2. SQL 是一種程式語言

了解 SQL 資料庫引擎運作方式的最佳方法是將 SQL 視為一種程式語言,而不是「查詢語言」。每個 SQL 陳述式都是一個獨立的程式。應用程式會建構 SQL 程式原始檔並將它們傳送至資料庫引擎。資料庫引擎會將 SQL 原始碼編譯成可執行格式,執行該可執行檔,然後將結果傳送回應用程式。

SQL 雖然是一種程式語言,但它與其他程式語言(例如 C、Javascript、Python 或 Go)不同,因為 SQL 是一種宣告式語言,而其他語言則是命令式語言。這是個重要的差異,會影響用於將程式原始碼轉譯成可執行格式的編譯器設計。不過,這些細節不應減損 SQL 其實只是另一種程式語言的事實。

2.1. 程式語言處理步驟

所有程式語言的處理步驟都分為兩步

  1. 將程式原始碼轉譯成可執行格式。

  2. 執行前一步驟所產生的可執行檔,以執行所需的動作。

所有程式語言都使用這兩個基本步驟。主要的差異在於可執行格式。

「編譯」語言(例如 C++ 和 Rust)會將原始碼轉譯成機器碼,而機器碼可直接由底層硬體執行。有些 SQL 資料庫系統會對 SQL 採取相同的做法,也就是將每個 SQL 陳述式直接轉譯成機器碼。但這種做法並不常見,也不是 SQLite 採用的做法。

其他語言(例如 Java、Perl、Python 和 TCL)通常會將程式原始碼轉譯成位元組碼。然後,這個位元組碼會透過讀取位元組碼並執行所需運算的直譯器執行。SQLite 使用這種位元組碼方法。如果您在 SQLite 中於任何 SQL 陳述式之前加上「EXPLAIN」關鍵字,它會顯示產生的位元組碼,而不是執行位元組碼。

另一種方法是將程式原始碼轉譯成記憶體中的物件樹狀結構。這個樹狀結構就是「可執行檔」。直譯器會透過遍歷樹狀結構來執行可執行檔。這是 MySQL、PostgreSQL 和 SQL-Server 使用的技術。

當然,並非每種語言都完全符合上述分類。這適用於 SQL 資料庫引擎和更熟悉的命令式程式語言。Javascript 以使用混合執行模型而聞名,其中程式碼最初編譯成物件樹,但可能會進一步轉譯(使用即時編譯)成更有效率的位元組碼或機器碼,作為提升效能的方法。

可執行檔案格式最終只是實作細節。重點是所有語言都有一個編譯步驟,將程式轉譯成可執行檔案格式,以及一個執行步驟,執行已編譯的程式。

2.2. 編譯 SQLite 程式

當將 SQL 程式提交至 SQLite 時,第一步是將原始文字分割成「記號」。記號可能是

空白和註解記號會被捨棄。所有其他記號都會輸入到 LALR(1) 剖析器 中,它會分析輸入程式的結構並為輸入程式產生 抽象語法樹 (AST)

剖析器將 AST 轉送至程式碼產生器。程式碼產生器是 SQLite 的核心,也是大部分魔法發生的地方。程式碼產生器會解析 AST 中的符號名稱,將輸入 SQL 中的欄位和表格名稱與資料庫中的實際欄位和表格配對。程式碼產生器也會對 AST 進行各種轉換以「最佳化」它。最後,程式碼產生器會選擇適當的演算法來實作 AST 要求的運算,並建構位元組碼來執行這些運算。

程式碼產生器所產生的位元組碼稱為「已準備好的陳述式」。將 SQL 原始碼轉換成已準備好的陳述式類似於透過呼叫 gcc 或 clang 將 C++ 程式轉換成機器碼。人類可讀的原始碼(SQL 或 C++)輸入,機器可讀的可執行檔(位元組碼或機器碼)輸出。

3. 進一步閱讀

此頁面最後修改於 2022-06-16 15:42:19 UTC