在SQLite中,主要有兩種表類型,帶rowid的表和不帶rowid的表。我們利用create table 建一張表,默認都會有一個隱含名字為rowid的主鍵,暫且稱帶rowid的表為普通表。如果建表時指定 WITHOUT ROWID屬性,那么建的表就是不帶rowid的表。那么這兩種表有什么區別?這篇文章主要討論這兩種表的存儲實現,以及它們的優缺點和適用的應用場景。
1.rowid是什么?
SQLite中rowid是一個隱身存儲的列,8個字節存儲,它有兩個別名 _ROWID_ 和 OID,類型定義為INTEGER PRIMARY KEY,因此當用戶建表時,某列定義為 INTEGER PRIMARY KEY,實質是rowid的別名。rowid是自增的,當該列插入null時, 會取當前表的最大值+1,作為該列的值。注意INTEGER與int不同,比如若列定義為 int primary key, 則插入null值,該列的值就是null,rowid依然會遞增。查詢可以通過select rowid from tablename得到rowid的值。為什么INTEGER與int不同,可以參考SQLite數據類型。
2.AUTOINCREMENT屬性
在SQLite中,AUTOINCREMENT屬性只能用於定義為INTEGER PRIMARY KEY的列,否則建表時會報錯。沒有AUTOINCREMENT屬性的rowid始終取當前最大值+1,若刪除了最大rowid所在的記錄,導致這個rowid會重用。 采用AUTOINCREMENT屬性可以避免重用情況,系統內部通過sqlite_sequence表來維護每個表的最大sequence值, 因此即使有刪除情況,也不會導致rowid重用,嚴格單調遞增,代價是執行插入時, 需要維護sqlite_sequence表,對性能有一定的損耗。 由於rowid采用8個字節存儲,因此上限值為9223372036854775807,當超過這個值時,rowid屬性會隨機選擇一個值, 只要不與表中已有記錄沖突即可;而AUTOINCREMENT屬性則會提示Error: database or disk is full。
3.存儲區別
普通表的PRIMRAY KEY實質是一個唯一索引,表數據按rowid組織(聚集索引), 通過主鍵訪問表,實質需要訪問唯一索引和聚簇索引,但對於INTEGER PRIMARY KEY除外, 它是rowid的一個別名,索引實質就是聚簇索引。在SQLite中,聚集索引采用B*樹存儲(B*樹是B+的一個特例,非葉子節點間也通過雙向指針相連),而普通索引(二級索引,唯一索引)采用B-樹存儲,B+樹與B樹的區別在於,B+樹中非葉子節點只有key信息,葉子節點包含了key和value信息,並且葉子節點包含了所有key信息,key信息在葉子節點和非葉子節點存儲了兩遍,葉子節點間有雙向指針相連;而B-樹中,葉子節點和非葉子節點結構相同,都包含了key和value信息,查找可能在非葉子節點找到數據,直接返回。
WITHOUT ROWID表采用B-Tree,葉子節點和非葉子節點都有記錄所有內容, 因此若記錄較長(超過page_size*1/20),扇出(節點記錄數)很小,容易造成節點頻繁分裂,不適合使用WITHOUT ROWID屬性, 。WITHOUT ROWID 表只有一顆B-樹,訪問只需要訪問一次B-樹, 而普通表需要訪問兩次(索引+表),對於INTEGER PRIMARY KEY除外。WITHOUT ROWID不支持AUTOINCREMENT屬性,並且PRIMARY KEY不能為null,普通表比較變態,PRIMARY KEY 屬性列也可以為null(由於歷史原因,沒有修改)。
4. 例子
(1).普通表
CREATE TABLE IF NOT EXISTS wordcount1( word TEXT PRIMARY KEY, cnt INTEGER );
wordcount1是一個普通表,底層采用兩顆B樹存儲,一顆B*樹存儲的是表內容,key為rowid,value為(word,cnt);另一顆B-樹是主鍵索引,key為(word,rowid)。因此對於每個word,都會在兩顆B樹中分別存一次。假設我們要查詢word為"xyzzy"的記錄:
SELECT cnt FROM wordcount1 WHERE word='xyzzy';
為了得到結果,首先需要通過主鍵索引找到rowid,然后再以rowid為key查找表,得到cnt,總共需要查找兩次B樹。
(2).WITHOUT ROWID表
CREATE TABLE IF NOT EXISTS wordcount2( word TEXT PRIMARY KEY, cnt INTEGER ) WITHOUT ROWID;
wordcount2是WITHOUT ROWID表,底層只有一顆B-樹,即主鍵索引,相對於wordcount1表,wordcount2表中word只需存儲一次。如果查詢word為"xyzzy"的記錄,只需查找一顆B樹即可。因此在這種情況下,WITHOUT ROWID表不僅節省了存儲,而且查詢效率也比普通表效率高。
4.如何選擇表類型?
1) 若主鍵為整型,采用普通表,將列定義為INTEGER PRIMARY KEY,這樣保證只有只有1顆B*樹,提高查詢效率;
2) 若主鍵為非整型,記錄比較小(不超過page_size*1/20),並且不依賴於rowid的邏輯序號,可以考慮使用WITHOUT ROWID表,節省空間 的同時,提高查詢效率
3) 其它情況,則使用普通表,定義主鍵。
5.參考文檔
https://www.sqlite.org/withoutrowid.html