SQLite 提供開發人員以任何所需的格式儲存內容的自由,而不受限於宣告的欄位資料類型。有些人覺得這個功能很麻煩。有些開發人員驚訝地發現,可以將文字插入標記為 INTEGER 的欄位中。
本文提倡 SQLite 中的彈性類型規則。
關於 SQLite 彈性類型系統的詳細資訊,請參閱個別的 SQLite 中的資料類型 文件。以下是快速摘要
欄位定義上的資料類型名稱是選用的。欄位定義可以只包含欄位名稱,而沒有其他內容。
當提供資料類型名稱時,它們可以是任何文字。SQLite 會嘗試根據欄位定義中的資料類型名稱推斷欄位的偏好資料類型,但該偏好資料類型僅供參考,並非強制性的。偏好資料類型稱為「欄位親和性」。
SQLite 會嘗試將輸入的資料轉換為欄位的偏好資料類型。(所有 SQL 資料庫引擎都會這樣做,而不僅僅是 SQLite。)如果轉換成功,一切順利。但如果轉換失敗,SQLite 不會引發錯誤,而是使用其原始資料類型儲存內容。
上述情況可能會導致嚴格類型擁護者覺得不方便
欄位資料類型 | 該欄位允許的類型 |
---|---|
INTEGER (整數) | INTEGER, REAL (實數), TEXT (文字), BLOB (二進位大型物件) |
REAL (實數) | REAL, TEXT, BLOB |
TEXT (文字) | TEXT, BLOB |
BLOB (二進位大型物件) | INTEGER, REAL (實數), TEXT (文字), BLOB (二進位大型物件) |
請注意,INTEGER 或 REAL 值永遠不會儲存在 TEXT 欄位中,因為 INTEGER 或 REAL 值可以而且總是會被轉換成其等效的 TEXT 表示形式。同樣地,INTEGER 永遠不會儲存在 REAL 欄位中,因為它總是會被轉換成 REAL。但 TEXT 並不總是看起來像 INTEGER 或 REAL 值,因此並非總是可以轉換。BLOB 無法轉換成任何其他類型,也沒有任何類型可以轉換成 BLOB。
有些讀者第一次接觸到 SQLite 中的彈性類型時,會問自己「這有什麼用?」以下嘗試回答這個問題
許多應用程式,尤其是那些使用 SQLite 作為應用程式檔案格式的應用程式,需要一個地方來儲存各種屬性,例如縮圖(BLOB 值)、簡短文字(例如使用者名稱),以及數值、日期和 JSON 值。建立單一表格來處理此儲存很方便
CREATE TABLE attribute(name TEXT PRIMARY KEY, value) WITHOUT ROWID;
如果沒有彈性類型,這樣的表格就需要更複雜,需要為每種可能的資料類型設置單獨的欄位。「值」欄位的彈性類型使表格在概念上更簡單、更節省空間,並且更容易存取和更新。
在 Fossil 版本控制系統 中,每個儲存庫都有一個 CONFIG 表,用於儲存各種設定,並支援所有可能的資料類型。Fossil 的使用者特定設定檔(~/.fossil 檔案)是一個獨立的 SQLite 資料庫,其中包含一個單一的屬性表,用於儲存所有儲存庫的使用者特定狀態。
一些應用程式使用 SQLite 資料庫作為純粹的鍵值儲存。資料庫綱要包含一個類似以下的單一表格:
CREATE TABLE storage(name TEXT PRIMARY KEY, value ANYTHING);
內建於 SQLite 的 json_tree 和 json_each 表值函式都有一個「value」欄位,可以根據對應 JSON 欄位的類型儲存 INTEGER、REAL 或 TEXT 類型的值。例如:
SELECT typeof(value) FROM json_each('{"a":1,"b":2.5,"c":"hello"}');
上述查詢返回三列一欄,值分別為「integer」、「real」和「text」。
分析師有時會遇到某些欄位包含整數、實數和文字資料混合的 CSV 檔案。例如,從 Excel 試算表匯出的 CSV 檔案通常具有此特性。將此類「髒資料」匯入 SQL 資料庫時,使用類型彈性的欄位進行匯入會很方便。
當然,髒資料不僅限於 Excel 輸出的 CSV 檔案。在許多資料來源中,單個欄位可能包含類型的混合。例如,資料欄位有時可能包含自 1970 年以來的秒數,或者在其他情況下包含文字日期字串。清理這些不一致的表示形式是可取的,但同時能夠將所有不同的表示形式儲存在中間資料庫的同一欄位中,以便進行清理也很方便。
SQLite 最初是作為 TCL 的擴充套件而開始的,後來才獨立發展。TCL 是一種動態語言,程式設計師不需要關心資料類型。在底層,TCL 會仔細追蹤每個值的資料類型,但對於 TCL 程式的開發人員和使用者來說,一切都像字串一樣。彈性類型非常適合與 TCL 等動態程式語言一起使用,因為使用動態程式語言時,您無法總是預先預測變數將保存的資料類型。因此,當您需要將該變數的值儲存到資料庫中時,使用支援彈性類型的資料庫會使儲存更容易。
每個 SQL 資料庫引擎似乎都有自己獨特的支援資料類型名稱集:
SQLite 接受所有這些名稱作為有效的類型名稱,並允許您將任何類型的內容儲存到欄位中,這增加了為在其他 SQL 資料庫引擎上執行的腳本也能在 SQLite 中執行的可能性。
由於 SQLite 資料庫檔案是磁碟上的單個檔案,因此某些應用程式使用 SQLite 作為應用程式檔案格式。這意味著一個應用程式實例在其生命週期中可能會與數百或數千個獨立的資料庫(每個資料庫都在一個單獨的檔案中)進行通訊。當這些應用程式經過多年的發展後,底層資料庫中某些欄位的含義會發生細微的變化。或者,可能需要重新利用現有欄位來滿足兩個或多個目的。如果欄位具有彈性資料類型,則更容易做到這一點。
以下列出的彈性類型缺點是從 Hacker News、Reddit 和類似開發者討論區的無數文章中收集和整理而來的。如果您能想到其他彈性類型不好的原因,請聯繫 SQLite 開發人員或在 SQLite 論壇 發帖,以便將您的想法添加到列表中。
許多對彈性類型持懷疑態度的人只是表示震驚和難以置信,卻沒有提供任何理由說明他們為什麼認為彈性類型是一個壞主意。沒有支持論點的情況下,我們必須假設他們不喜歡彈性類型的原因是它與他們習慣的不同。
據推測,許多對 SQLite 的彈性類型感到驚駭的開發人員之所以會有這種感覺,是因為他們以前從未遇到過類似的情況。先前接觸到的所有資料庫,尤其是 SQL 資料庫,都涉及嚴格的類型,而讀者對 SQL 的心智模型也將嚴格類型視為一個基本特性。彈性類型顛覆了他們的世界觀。
是的,彈性類型是一種在 SQL 資料庫中思考資料的新方式。但新事物不一定是壞事。有時,我認為尤其是在彈性類型的情況下,創新會帶來改進。
許多程式設計師都信奉一個教條,即防止應用程式錯誤的最佳方法是嚴格的類型強制。但我沒有找到任何證據支持這一點。
可以肯定的是,嚴格的類型強制確實有助於防止低階語言(如 C 和 C++)中出現某些類型的錯誤,這些語言提供的模型接近機器硬體。但對於高階抽象語言來說,情況似乎並非如此,在這些語言中,所有資料都以某種「值」超類的形式傳遞,而該超類是各種低階資料類型的子類。當所有東西都是一個值物件時,特定的資料類型就不再重要了。
本技術說明由 SQLite 的原作者撰寫。我已經編寫了 27 年的 TCL 程式。TCL 完全沒有類型強制。「值」類別在 TCL 中(稱為 Tcl_Obj)可以容納許多不同的資料類型,但它將內容以字串的形式呈現給程式和應用程式使用者。這些年來,我在這些 TCL 程式中遇到過很多錯誤。但我記不起有任何一個錯誤可能是被嚴格的類型系統捕捉到的。在過去的 35 年裡,我也寫了很多 C 程式碼,其中 SQLite 本身就是其中之一。我發現 C 語言中的類型系統在發現和防止問題方面非常有幫助。對於以 C 語言編寫的 Fossil 版本控制系統,我甚至實作了額外的靜態分析程式,在編譯之前掃描 Fossil 原始程式碼,尋找編譯器遺漏的問題。這對於編譯程式來說非常有效。
SQL 語言模型比 C/C++ 更為抽象。在 SQLite 中,每個資料項都以「sqlite3_value」物件的形式儲存在記憶體中。這個物件有字串、整數、浮點數、BLOB 和其他表示形式的子類。在 SQLite 實作的 SQL 語言內部,所有資料都以「sqlite3_value」物件的形式傳遞,因此底層資料類型並不重要。我從未發現嚴格的類型強制在像 TCL 和 SQLite 這樣使用單一「值」超類來表示任何資料元素的語言中有幫助。Fossil 在其實作中廣泛使用了 SQLite。在 Fossil 14 年的歷史中,出現過許多錯誤,但我記不起有任何一個錯誤可能是被 SQLite 中的嚴格類型強制所阻止的。一些 C 語言錯誤可能可以通過更好的類型強制來捕捉(這就是我編寫額外原始程式碼掃描器的原因),但沒有 SQL 錯誤。
基於數十年的經驗,我反對嚴格類型強制有助於防止應用程式錯誤的論點。我可以接受並相信一個稍微修改過的論點:嚴格類型強制有助於防止*缺乏單一頂層「值」超類的語言*中的應用程式錯誤。但 SQLite 確實擁有單一「sqlite3_value」超類,因此該諺語不適用。
有些人認為,如果您對 schema 有嚴格的約束,尤其是對欄位資料類型的嚴格強制執行,這將有助於防止將不正確的資料新增到資料庫中。這是不正確的。類型強制執行可能有助於防止*明顯*不正確的資料進入系統,這是事實。但類型強制執行對於防止記錄略微不正確的資料沒有幫助。
因此,例如,嚴格類型強制可以成功防止將客戶名稱(文字)插入到整數 Customer.creditScore 欄位中。另一方面,如果發生該錯誤,則很容易發現問題並找到所有受影響的列。但類型強制對於防止客戶姓氏和名字顛倒的錯誤沒有幫助,因為兩者都是文字欄位。
透過抑制容易檢測的錯誤,只讓難以檢測的錯誤通過,嚴格類型強制實際上會使查找和修復錯誤變得更加困難。資料錯誤往往會聚集。如果您有 20 個不同的資料來源,大多數資料錯誤通常只來自其中的 2 或 3 個來源。明顯錯誤(例如整數欄位中的文字)的存在是一個方便的早期警訊,表明出現了問題。可以快速追蹤問題的來源,並對明顯錯誤的來源進行額外審查,從而希望能同時修復細微的錯誤。當明顯錯誤被抑制時,您會失去一個重要的訊號,該訊號可幫助您檢測和修復細微的錯誤。
資料錯誤是不可避免的。無論進行多少類型檢查,它們都會發生。嚴格類型強制只能捕獲一小部分情況——最明顯的情況。它對於查找和修復更細微的情況沒有任何幫助。而且,透過抑制哪些資料來源有問題的訊號,它有時會使細微的錯誤更難以定位。
由於 SQLite 的限制較少,允許您執行更多操作,因此適用於其他資料庫引擎的 SQL 指令碼通常也適用於 SQLite,但最初為 SQLite 編寫的指令碼可能不適用於限制較多的資料庫引擎。當開發人員使用 SQLite 進行原型設計和測試,然後將其應用程式遷移到限制較多的 SQL 引擎進行部署時,這可能會導致問題。如果應用程式(無意中)利用了 SQLite 中提供的彈性類型,那麼在遷移時它將會失敗。
人們利用這個問題來論證 SQLite 應該對資料類型更加嚴格。但您也可以反過來論證,其他資料庫引擎應該在資料類型方面更具彈性。畢竟,在遷移之前,該應用程式在 SQLite 下正常運行。如果嚴格類型強制真的那麼有用,為什麼它會破壞先前正常運行的應用程式?
從 SQLite 3.37.0 版(2021 年 11 月 27 日)開始,SQLite 使用STRICT 表格支援這種開發風格。
如果您發現 STRICT 表格防止或本來可以防止應用程式錯誤的真實案例,請在SQLite 論壇上發布訊息,以便我們將您的故事新增到本文檔中。
如果您對 SQL 資料庫中的彈性類型還不熟悉,我鼓勵您嘗試一下。它可能不會造成任何問題,而且它可能會讓您的程式更簡潔、更容易編寫和維護。我認為即使您一開始抱持懷疑態度,只要您願意嘗試彈性類型,最終您會意識到這是一個更好的方法,並且會開始鼓勵其他資料庫供應商至少支援 ANY 資料類型,即使不是像 SQLite 這樣的完全彈性類型。
大多數情況下,彈性類型並不重要,因為一個欄位通常只儲存單一、定義明確的類型。但偶爾您會遇到一些情況,這時擁有彈性類型系統會讓您的解決方案更清晰、更簡單。
本頁最後修改時間:2024-07-14 22:39:43 UTC