1. 簡介
部分索引是針對資料表子集列的索引。
在一般索引中,資料表中的每一列在索引中只有一個項目。在部分索引中,資料表中只有部分子集列有對應的索引項目。例如,部分索引可能會省略索引列為 NULL 的項目。明智地使用部分索引,可以縮小資料庫檔案大小,並改善查詢和寫入效能。
2. 建立部分索引
在一般 CREATE INDEX 語句的結尾加上 WHERE 子句,即可建立部分索引。
create-index-stmt
hide
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
schema-name
.
index-name
ON
table-name
(
indexed-column
)
,
WHERE
expr
expr
show
literal-value
bind-parameter
schema-name
.
table-name
.
column-name
unary-operator
expr
expr
binary-operator
expr
function-name
(
function-arguments
)
filter-clause
over-clause
(
expr
)
,
CAST
(
expr
AS
type-name
)
expr
COLLATE
collation-name
expr
NOT
LIKE
GLOB
REGEXP
MATCH
expr
expr
ESCAPE
expr
expr
ISNULL
NOTNULL
NOT
NULL
expr
IS
NOT
DISTINCT
FROM
expr
expr
NOT
BETWEEN
expr
AND
expr
expr
NOT
IN
(
select-stmt
)
expr
,
schema-name
.
table-function
(
expr
)
table-name
,
NOT
EXISTS
(
select-stmt
)
CASE
expr
WHEN
expr
THEN
expr
ELSE
expr
END
raise-function
filter-clause
show
function-arguments
show
DISTINCT
expr
,
*
ORDER
BY
ordering-term
,
ordering-term
show
expr
COLLATE
collation-name
DESC
ASC
NULLS
FIRST
NULLS
LAST
literal-value
show
CURRENT_TIMESTAMP
numeric-literal
string-literal
blob-literal
NULL
TRUE
FALSE
CURRENT_TIME
CURRENT_DATE
over-clause
show
OVER
window-name
(
base-window-name
PARTITION
BY
expr
,
ORDER
BY
ordering-term
,
frame-spec
)
frame-spec
show
GROUPS
BETWEEN
UNBOUNDED
PRECEDING
AND
UNBOUNDED
FOLLOWING
RANGE
ROWS
UNBOUNDED
PRECEDING
expr
PRECEDING
CURRENT
ROW
expr
PRECEDING
CURRENT
ROW
expr
FOLLOWING
expr
PRECEDING
CURRENT
ROW
expr
FOLLOWING
EXCLUDE
CURRENT
ROW
EXCLUDE
GROUP
EXCLUDE
TIES
EXCLUDE
NO
OTHERS
ordering-term
show
expr
COLLATE
collation-name
DESC
ASC
NULLS
FIRST
NULLS
LAST
raise-function
show
RAISE
(
ROLLBACK
,
error-message
)
IGNORE
ABORT
FAIL
select-stmt
show
WITH
RECURSIVE
common-table-expression
,
SELECT
DISTINCT
result-column
,
ALL
FROM
table-or-subquery
join-clause
,
WHERE
expr
GROUP
BY
expr
HAVING
expr
,
WINDOW
window-name
AS
window-defn
,
VALUES
(
expr
)
,
,
compound-operator
select-core
ORDER
BY
LIMIT
expr
ordering-term
,
OFFSET
expr
,
expr
common-table-expression
show
table-name
(
column-name
)
AS
NOT
MATERIALIZED
(
select-stmt
)
,
compound-operator
show
UNION
UNION
INTERSECT
EXCEPT
ALL
join-clause
show
table-or-subquery
join-operator
table-or-subquery
join-constraint
join-constraint
show
USING
(
column-name
)
,
ON
expr
join-operator
show
NATURAL
LEFT
OUTER
JOIN
,
RIGHT
FULL
INNER
CROSS
ordering-term
show
expr
COLLATE
collation-name
DESC
ASC
NULLS
FIRST
NULLS
LAST
result-column
show
expr
AS
column-alias
*
table-name
.
*
table-or-subquery
show
schema-name
.
table-name
AS
table-alias
INDEXED
BY
index-name
NOT
INDEXED
table-function-name
(
expr
)
,
AS
table-alias
(
select-stmt
)
(
table-or-subquery
)
,
join-clause
window-defn
show
(
base-window-name
PARTITION
BY
expr
,
ORDER
BY
ordering-term
,
frame-spec
)
frame-spec
show
GROUPS
BETWEEN
UNBOUNDED
PRECEDING
AND
UNBOUNDED
FOLLOWING
RANGE
ROWS
UNBOUNDED
PRECEDING
expr
PRECEDING
CURRENT
ROW
expr
PRECEDING
CURRENT
ROW
expr
FOLLOWING
expr
PRECEDING
CURRENT
ROW
expr
FOLLOWING
EXCLUDE
CURRENT
ROW
EXCLUDE
GROUP
EXCLUDE
TIES
EXCLUDE
NO
OTHERS
type-name
show
name
(
signed-number
,
signed-number
)
(
signed-number
)
signed-number
show
indexed-column
show
column-name
COLLATE
collation-name
DESC
expr
ASC
結尾包含 WHERE 子句的任何索引都被視為部分索引。省略 WHERE 子句的索引(或在 CREATE TABLE 語句中由 UNIQUE 或 PRIMARY KEY 約束建立的索引)是一般完整索引。
WHERE 子句之後的表達式可能包含運算子、文字值和正在建立索引的表格中的欄位名稱。WHERE 子句不能 包含子查詢、對其他表格的參照、非確定性函數 或繫結參數 。
只有 WHERE 子句評估為 true 的表格列會包含在索引中。如果 WHERE 子句表達式評估為 NULL 或對表格的某些列為 false,則這些列會從索引中省略。
部分索引的 WHERE 子句中參照的欄位可以是表格中的任何欄位,而不僅僅是碰巧建立索引的欄位。然而,部分索引的 WHERE 子句表達式通常是對正在建立索引的欄位進行的簡單表達式。以下是一個典型的範例
CREATE INDEX po_parent ON purchaseorder(parent_po) WHERE parent_po IS NOT NULL;
在上面的範例中,如果大多數的採購訂單沒有「父系」採購訂單,則大多數 parent_po 值會為 NULL。這表示只有採購訂單表格中的一小部分列會建立索引。因此,索引佔用的空間會少很多。而且,對原始採購訂單表格的變更會執行得更快,因為 po_parent 索引只需要針對 parent_po 不為 NULL 的那些例外列進行更新。但索引對於查詢仍然很有用。特別是,如果某人想要知道特定採購訂單「?1」的所有「子系」,則查詢會是
SELECT po_num FROM purchaseorder WHERE parent_po=?1;
上面的查詢會使用 po_parent 索引來協助找出答案,因為 po_parent 索引包含所有相關列的項目。請注意,由於 po_parent 比完整索引小,因此查詢可能會執行得更快。
2.1. 唯一部分索引
部分索引定義可以包含 UNIQUE 關鍵字。如果包含,則 SQLite 要求在索引中 的每個項目都是唯一的。這提供了一種機制,用於強制在表格中某些列的子集中執行唯一性。
例如,假設你有一個大型組織成員的資料庫,其中每個人都被分配到一個特定的「團隊」。每個團隊都有「領導者」,他也是該團隊的成員。該表格可能如下所示
CREATE TABLE person(
person_id INTEGER PRIMARY KEY,
team_id INTEGER REFERENCES team,
is_team_leader BOOLEAN,
-- other fields elided
);
team_id 欄位無法唯一,因為通常同一個團隊有多人。由於每個團隊通常有多個非領導者,因此無法使 team_id 和 is_team_leader 的組合唯一。執行每個團隊一個領導者的解決方案是在 team_id 上建立唯一索引,但僅限於 is_team_leader 為 true 的那些條目
CREATE UNIQUE INDEX team_leader ON person(team_id) WHERE is_team_leader;
巧合的是,同一個索引對於找到特定團隊的團隊領導者也很有用
SELECT person_id FROM person WHERE is_team_leader AND team_id=?1;
3. 使用部分索引的查詢
讓 X 成為部分索引的 WHERE 子句中的表達式,讓 W 成為使用已編制索引的表格的查詢的 WHERE 子句。然後,如果 W⇒X,則允許查詢使用部分索引,其中 ⇒ 運算子(通常發音為「暗示」)是邏輯運算子,等於「X 或非 W」。因此,確定部分索引是否可用於特定查詢會簡化為證明一階邏輯中的定理。
SQLite 沒有 一個精密的定理證明器來確定 W⇒X。相反,SQLite 使用兩個簡單的規則來找出 W⇒X 為真的常見情況,並假設所有其他情況都為假。SQLite 使用的規則如下
如果 W 是 AND 連接的術語,而 X 是 OR 連接的術語,並且 W 的任何術語出現在 X 的術語中,則部分索引可用。
例如,讓索引為
CREATE INDEX ex1 ON tab1(a,b) WHERE a=5 OR b=6;
讓查詢為
SELECT * FROM tab1 WHERE b=6 AND a=7; -- uses partial index
然後,索引可被查詢使用,因為「b=6」一詞同時出現在索引定義和查詢中。請記住:索引中的詞彙應以 OR 相連,而查詢中的詞彙應以 AND 相連。
W 和 X 中的詞彙必須完全相符。SQLite 不會進行代數運算以嘗試讓它們看起來相同。「b=6」一詞不符合「b=3+3」、「b-6=0」或「b BETWEEN 6 AND 6」。只要索引中存在「b=6」,而查詢中存在「6=b」,則「b=6」將符合「6=b」。如果索引中出現「6=b」形式的詞彙,它將永遠不符合任何內容。
如果 X 中的詞彙為「z IS NOT NULL」形式,而 W 中的詞彙是「z」上的比較運算子(「IS」除外),則這些詞彙相符。
範例:讓索引為
CREATE INDEX ex2 ON tab2(b,c) WHERE c IS NOT NULL;
然後,任何在「c」欄位上使用運算子 =、<、>、<=、>=、<>、IN、LIKE 或 GLOB 的查詢都可用於部分索引,因為這些比較運算子僅在「c」不為 NULL 時才為真。因此,下列查詢可以使用部分索引
SELECT * FROM tab2 WHERE b=456 AND c<>0; -- uses partial index
但下一個查詢無法使用部分索引
SELECT * FROM tab2 WHERE b=456; -- cannot use partial index
後一個查詢無法使用部分索引,因為表格中可能存在 b=456 且 c 為 NULL 的列。但這些列不會出現在部分索引中。
這兩個規則描述了撰寫本文時(2013-08-01)SQLite 的查詢規劃器的運作方式。而且上述規則將永遠被遵守。然而,SQLite 的未來版本可能會納入更好的定理證明器,它可以找出 W⇒X 為真的其他案例,因此可能會找到更多部分索引有用的實例。
4. 支援版本
自 版本 3.8.0 (2013-08-26)以來,SQLite 已支援部分索引。
包含部分索引的資料庫檔案無法由 3.8.0 之前的 SQLite 版本讀取或寫入。但是,由 SQLite 3.8.0 建立的資料庫檔案仍然可以由之前的版本讀取和寫入,只要其架構不包含任何部分索引即可。無法由舊版 SQLite 讀取的資料庫,只要對部分索引執行 DROP INDEX ,即可讓其可讀取。