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