SQLite 中的各種限制
在這篇文章的環境里,“限制”意味著不能超過設定的大小或者數量。我們關心的是諸如在BLOB或者表中最大字節數。
SQLite最初被設計成避免任何限制策略的數據庫。當然,運行任意程序的機器都有特定大小內存和磁盤空間限制。但是在SQLite,哪些限制沒有被定義。該策略是,如果有足夠內存并且可以運行在32位機器上,那么它應該可以工作。
不幸的是,沒有限制策略已經暴露出問題了。因為上限沒有定義,它們沒經過(極限)測試,當把SQLite推向極限時,漏洞(包括潛在的安全隱患)經常出現。鑒于此原因,SQLite的新版本明確定義了限制,并且那些限制作為單元測試的一部分。
本文定義了 SQLite 的限制,如何針對這些限制定制特定的應用程序。默認的限制設置通常是適當的,幾乎適合于每一個應用。有一些應用程序可能需要在這里或者那里增加一個設置,但是我們估計這非常罕見。更普遍的是,應用程序可能需要重新編譯SQLite以及更低的限制來避免過多的資源利用率,以及在高級SQL語句生成器上幫助阻止攻擊者注入惡意SQL語句時發生錯誤。
在使用 sqlite3_limit() 接口的 limit categories 上,為該接口定義一些限制,可以在運行時改變每個基礎的連接。應用程序設計的運行時限制多數據庫,一些僅供內部使用的限制可以影響或控制潛在的敵對外部代理。舉例來說,一個web瀏覽器應用程序可能使用一個內部的數據庫來追蹤歷史頁面瀏覽量,但是它有一個或很多分離的數據庫,它們被創建和控制是通過 javascript 應用,這些應用都是從互聯網上下載的。那么 sqlite3_limit() 接口是允許通過可信代碼來管理約束內置數據庫的,同時在數據庫創建或控制上有著嚴格的限制,它會拒絕不可信的外部代碼攻擊服務。
1、string或者BLOB的最大長度
SQLite中string或者BLOB的最大字節數是由預處理器宏SQLITE_MAX_LENGTH定義的。這個宏的默認值是10億,你可以在編譯時使用像下面這樣的命令行參數來對這個默認值進行調整:
-DSQLITE_MAX_LENGTH=123456789
在當前實現中僅支持將string或者BLOB長度上調到最大231-1 or 2147483647。并且這個時候一些內置的函數例如hex()將會調用失敗。在安全敏感的應用中最好不要嘗試增加string和BlOB的最大長度。實際上,如果可以的話,你可以將string和BLOB的最大長度在一定范圍內降低(幾百方字節)。
在SQLite的INSERT和SELECT處理時,數據庫中中的每一行的所有內容都被編碼成單個BLOB。所以SQLITE_MAX_LENGTH這個參數同樣也定義了一行的最大字節數。string或者BLOB的最大長度可以在運行時通過sqlite3_limit(db,SQLITE_LIMIT_LENGTH,size) 方法調低。
2、最大列數
SQLITE_MAX_COLUMN在編譯時用來設置一個上限:默認設置SQLITE_MAX_COLUMN的值是2000。你可以在編譯的時候將它調整到最大32767。另一方面,許多經驗豐富的數據庫設計者會認為一個設計良好的數據庫永遠都不會在表中需要超過100列。
在大多數應用中的列數是很小的,大約幾十個而已。在SQLite代碼生成器中使用的算法是O(N2),這個N就是列數。所以如果你重新定義SQLITE_MAX_COLUMN為一個巨大的數字,那么在生成SQL的時候使用這個大列數你就會發現sqlite3_prepare_v2() 運行的很慢。最大列數可以在運行時使用sqlite3_limit(db,SQLITE_LIMIT_COLUMN,size) 方法調低。
- 表中的列數
- 索引中的列數
- 視圖中的列數
- UPDATE語句中SET子句中的項數
- SELECT語句結果集中的列數
- GROUP BY 或者 ORDER BY子句中的項數
- INSERT語句中的value項數
-
SQL語句的最大長度一個SQL語句文本中字節數的最大值,受限于 SQLITE_MAX_SQL_LENGTH ,其默認為 1000000。你可以重新對這個限制進行定義,大到 SQLITE_MAX_LENGTH 和 1073741824 兩者中較小的一個值。如果一個SQL語句在長度上被限制在1百萬個字節以內,那么很明顯你就不能夠以字符串字面量的形式將幾百萬字節嵌入到 INSERT 語句中。不過你應該是不會那樣做的。這時候針對這些數據你可以使用占位參數,像下面這樣先準備好一個簡短的SQL語句:
INSERT INTO tab1 VALUES(?,?,?);
然后使用 sqlite3 的_bind_XXXX() 函數來將大型的字符串值綁定到這個SQL語句。綁定的使用回避掉了要在字符串中對引號進行轉義的必要, 同時降低了遭受SQL注入攻擊的風險。它運行起來也更快,因為大型的字符串不必進行其它方式要進行的多次轉換和復制操作。
SQL語句的最大長度可以在運行時使用 sqlite3 的_limit(db,SQLITE_LIMIT_SQL_LENGTH,size) 接口來減小。
-
一次連接操作中最大的表數量 SQLite 不支持超過64個表的連接操作。此限制源于在查詢優化器中,SQLite代碼生成器會使用每個連接表一個位的位圖,這一事實。SQLite 使用了一種高效的 查詢規劃器算法 ,因此即使是一個大型地連接操作也能被快速地被 預處理好。 所以沒有任何機制來提高或者降低一次連接操作中表的數量。
-
表達式樹結構的最大深度 SQLite 會將表達解析成一個數結構來進行處理。在代碼生成期間,SQLite會以遞歸的形式遍歷這個樹結構。表達式樹結構的深度因此被加上了限制,以此避免用掉太多的棧空間。SQLITE_MAX_EXPR_DEPTH 參數決定了表達式樹結構的最大深度。如果其值為 0,就表示不加任何限制。目前的實現所使用的默認值是 1000。
如果 SQLITE_MAX_EXPR_DEPTH 初始是正數,那么表達書樹結構的最大深度可以在運行時使用 sqlite3 的_limit(db,SQLITE_LIMIT_EXPR_DEPTH,size) 接口來降低。換言之,如果已經在編譯時對表達式深度做了限制,那么表達式樹結構深度的最大值就可以在運行時被降低。如果If SQLITE_MAX_EXPR_DEPTH 的值在編譯時被設置為 0(即表達式的深度不受限制),那么sqlite3 的_limit(db,SQLITE_LIMIT_EXPR_DEPTH,size) 接口就是一個無效操作。
-
函數里慘呼是的最大數量 SQLITE_MAX_FUNCTION_ARG 參數決定了能夠被傳入到一個SQL函數的參數的最大數量。這一限制的默認值為100。SQLite 應該在函擁有數以千計的參數時也能運作。不過,我們對于那些使用過多參數的人持懷疑態度,因為他們可能是在嘗試找出使用了SQLite的系統中的安全漏洞,而不是做一些實用的事情, 而因為這個原有,我們已經給這個參數設置了相對而言較低的值。傳入函數的參數數量有時會被存儲在一個有符號的字符中,因此 SQLITE_MAX_FUNCTION_ARG 有一個限定死的 127 的上限。一個函數中的參數的最大數量可以在運行時使用 sqlite3 的_limit(db,SQLITE_LIMIT_FUNCTION_ARG,size) 接口來降低。
-
一個復合SELECT語句中段落的最大數量 一個復合 SELECT 語句就是那種由操作符 UNION, UNION ALL, EXCEPT, 或者 INTERSECT 連接起來的兩個或者更多個SQL語句。我們將一個復合SELECT中的每一個獨立的SELECT語句稱為一個“段落”。SQLite中的代碼生成器會使用一種遞歸算法來對復合SELECT語句進行處理。對棧的大小有必要進行一下限制,我們會因為這個緣故對復合SELECT中的段落數量進行限制。段落的最大數量限制參數就是 SQLITE_MAX_COMPOUND_SELECT ,其默認值為 500。我們認為這已經是一個比較寬裕的分配方案了,因為在實際使用中很少會遇到一個復合SELECT中段落的數量超過個位數。復合SELECT段落的最大數量在運行時可以使用 sqlite3 的_limit(db,SQLITE_LIMIT_COMPOUND_SELECT,size) 接口來降低。
-
LIKE 或者 GLOB 模式的最大數量 在特定的一些極端場景中,SQLite默認的LIKE和GLOB實現中所使用的模式匹配算法會表現出 O(N2) 的性能消耗(這里的N指的是模式中字符的數量。為了避免遭受來自那些能夠自己指定 LIKE 或者 GLOB 模式的人所進行的拒絕服務攻擊, LIKE 或者 GLOB 模式的長度被 SQLITE_MAX_LIKE_PATTERN_LENGTH 所指定的位數值進行了限定。這個的默認值為 50000。現代的工作站能夠以相對較快的速度計算出一個擁有50000位這樣極端長度的 LIKE 或者 GLOB 模式。拒絕服務攻擊的問題只有在模式長度達到百萬個字節位數這樣的程度時才會造成影響。不過因為大多數實用的 LIKE 或者 GLOB模式長度大多只有幾十個字節,偏執的開發者如果知道外部用戶擁有生成任意模式的能力,也許就會想要把這個參數限制到幾百個這樣的范圍之內。LIKE 或者 GLOB 模式的最大長度可以在運行時使用 sqlite3 的_limit(db,SQLITE_LIMIT_LIKE_PATTERN_LENGTH,size) 接口來減小。
-
單個SQL語句中預置參數的最大個數 預置參數就是SQL語句中的一個占位符,將來會被使用sqlite3 其中的一個_bind_XXXX() 接口填充。許多SQL程序員對于使用問號 ("?") 作為預置參數都很熟悉。SQLite 也支持以前置 ":", "$", 或者 "@"的命名作為預置參數,并且以“?123”這樣的形式對預置參數進行編號。SQLite語句中的每一個預置參數都被分配了一個數字。該數字一般以1開始,而后對于每一個新的參數其數字加一。然而,當“?123”這樣的形式被使用了的時候,預置參數的編號就會是問號后面跟著的那個數字。SQLite 會給從1到最大預置參數數量之間的預置參數分配空間。因此,一個包含了像 ?1000000000 這樣的預置參數的SQL就會需要千兆字節的存儲。這樣就會很容易地超過主機的資源供應能力。為了防止這種過度了內存分配,預置參數的最大數量就要限制到 SQLITE_MAX_VARIABLE_NUMBER 這樣一個固定值,其默認為 999。預置參數的最大數量可以在運行時使用 sqlite3 的_limit(db,SQLITE_LIMIT_VARIABLE_NUMBER,size) 接口來減小。
-
觸發器遞歸的最大深度 SQLite 限制了觸發器的遞歸深度,一次來阻止一個涉及到遞歸觸發器的語句毫無限度的使用內存。版本 3.6.18之前的SQLite,觸發器并非遞歸的,因此這一限制毫無意義。從版本3.6.18開始,遞歸觸發器得到了支持,但必須使用 PRAGMA recursive_triggers 語句來明確聲明啟用了它。從版本3.7.0開始,遞歸觸發器就是默認被啟用了的,但可以使用 PRAGMA recursive_triggers 來手動禁用。SQLITE_MAX_TRIGGER_DEPTH 只在其啟用時有效。
觸發器遞歸的默認的最大深度為1000。
從屬數據庫的最大數量
ATTACH 語句是一項SQLite擴展,它能讓兩個或者更多個數據庫與同一個數據庫連接聯合起來,操作起來就好像它們是一個數據庫。能夠被聯合的數據庫數量被限制到了 SQLITE_MAX_ATTACHED 這樣一個常量,其默認值為10。被聯合數據庫的最大數量不能超過125。
被聯合的數據庫的最大數量可以在運行時使用 sqlite3_limit(db,SQLITE_LIMIT_ATTACHED,size) 接口來減小。
一個數據庫文件中頁面的最大數量
SQLite 能夠限制數據庫文件的大小,以阻止數據庫文件變得太大而消耗過多的磁盤空間。SQLITE_MAX_PAGE_COUNT 參數,一般會被設置成 1073741823, 就是一個數據庫文件中頁面的最大數量。一次會造成數據庫文件增長超過這個值的插入新數據的操作將會返回 SQLITE_FULL。
SQLITE_MAX_PAGE_COUNT 最大的可能設置是 214748364。當最大頁面大小為 65536 時, 最大的SQLite數據庫大小就大概是140太字節。最大頁面總數PRAGMA 可以被用來在運行時提高或者降低這個限制。
表中數據行的最大數量
表中數據行的最大數量理論上可以到 264 (18446744073709551616 or about 1.8e+19)。這一限制是不可及的,因為數據庫會首先達到140太字節的大小限制。一個140太字節大小的數據庫可以容納超過將近 1e+13 行的數據,而要容納這么多行數據,只能在沒有指數數據存在,并且每一行只包含非常少量數據的時候。
最大數據庫大小
每個數據庫都包含一個或多個“頁”(page)。在某個單一數據庫中,每個頁的大小相同,但是不同的數據庫擁有不同的頁大小,大小介于512字節(含)和65536字節(含)之間。一個數據庫文件最多包括2147483646頁。每個頁最大65536字節,換算可得最大數據庫大小約為1.4e+14字節(140千萬兆字節或128太字節或140000百萬兆字節或128000吉字節)。由于開發人員沒有能滿足這個限制的硬件條件,所以沒有對這個上限進行測試。然而,測試表明當數據庫達到底層文件系統的最大文件大小(通常要比理論上的最大數據庫大小小很多)并且由于磁盤空間耗盡而無法擴展時,SQLite依然能正確并穩健地運行。
模式中表的最大數量
數據庫文件中的每個表和索引至少需要一個頁。這里的索引可以使用CREATE INDEX 語句顯式地創建,也可以通過UNIQUE和PRIMARY KEY約束隱式地創建。由于數據庫文件的頁的最大數量是2147483646(比20億還多),所以模式中表和索引的數量上限也是這個。
只要數據庫被打開,就會掃描和解析整個模式,并將模式的解析樹存儲在內存中。也就是說數據庫的啟動時間和初始內存使用率與模式大小成正比。