當查詢結果時不只是單純讀取資料,而是還需要額外做轉換或運算時,效能可能會受到影響。但其實我們可以善用 Index,讓這些轉換在查詢前就先「準備好」,查詢的速度可能就會加快。
舉例來說,如果某個欄位在查詢時經常需要轉為小寫再比對,我們就可以在建立 Index 的時候,直接針對轉換後的小寫值建立 Index,讓查詢時不必每次都即時運算,這個稱為 Expression index。
例如,假如希望在查詢 quizzes
表中的 school_type
時,能夠忽略大小寫,並且都用小寫來查詢,可以直接將小寫的 school_type
建立成 Index:
CREATE INDEX quizzes_lower_school_type_idx ON quizzes (lower(school_type));
接著進行查詢時,我們可以看到它能夠直接使用到建立好的 Index:
EXPLAIN ANALYZE
SELECT * FROM quizzes WHERE lower(school_type) = 'elementary';
我們來比較看看,不將小寫版本的值當作 Index,而是直接將原始的 school_type
欄位設為 Index 時的速度。先刪除原來的 Index:
DROP INDEX quizzes_lower_school_type_idx;
然後,重新建立 Index,不使用 lower()
函數:
CREATE INDEX quizzes_lower_school_type_idx ON quizzes(school_type);
再次查詢時,會發現他不會使用到 index 了!這是因為 lower()
會在比較前就改變了原本的值,導致沒辦法使用原本 B-Tree 內的值。
有些情況下,查詢條件需要將多個欄位組合在一起,例如:
SELECT * FROM people WHERE (first_name || ' ' || last_name) = 'John Smith';
從上面的測試可以發現,就算把 first_name
和 last_name
這兩個欄位建立了 Index,這樣的查詢條件是沒辦法使用的到 Index 的。
-- 建立 table
CREATE TABLE people (
id SERIAL PRIMARY KEY,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL
);
-- 建立沒有組合過資料的 index
CREATE INDEX idx_people_name ON people (first_name, last_name);
-- 確認是否有使用到 index
EXPLAIN ANALYZE
SELECT * FROM people WHERE (first_name || ' ' || last_name) = 'book growth';
因此為了提高查詢速度,我們可以將組合欄位 (first_name || ' ' || last_name)
本身設為 Index:
CREATE INDEX people_names ON people ((first_name || ' ' || last_name));
這樣再次查詢時,就可以發現搜尋因為能使用到 index 變快了!
不過要注意速度變快的原因,是因為轉換的步驟在 insert / update 的時候就先做完了,因此時間會分散在每次 insert / update 的時候,換來的是查詢的速度可以變得更快喔~
Indexes on expressions are useful when retrieval speed is more important than insertion and update speed.
https://www.postgresql.org/docs/current/indexes-expressional.html