最近面試的時候,總會遇到一個問題
在 PostgreSQL 中,聯合索引在什么條件下會生效?
特此記錄~
前置信息
數據庫版本
- PostgreSQL 14.13, compiled by Visual C++ build 1941, 64-bit
建表語句
CREATE TABLE people (id SERIAL PRIMARY KEY,city VARCHAR(50),name VARCHAR(50),age INT
);CREATE INDEX idx_city_name_age ON people(city, name, age);-- 插入數據
INSERT INTO people (city, name, age) VALUES
('Beijing', 'Tom', 18),
('Beijing', 'Tom', 20),
('Beijing', 'Jerry', 18),
('Shanghai', 'Jerry', 22),
('Shanghai', 'Alice', 25),
('Guangzhou', 'Bob', 30);
查看索引是否命中
EXPLAIN ANALYZE SELECT * FROM people WHERE city = 'Beijing' AND name = 'Tom' AND age = 20;
結果
Index Scan using idx_city_name_age on people (cost=0.15..8.17 rows=1 width=244) (actual time=0.012..0.013 rows=1 loops=1)Index Cond: (((city)::text = 'Beijing'::text) AND ((name)::text = 'Tom'::text) AND (age = 20))
Planning Time: 0.074 ms
Execution Time: 0.022 ms
我們可以清楚的看到索引命中了,那么改變一下 WHERE 條件呢,例如:
EXPLAIN ANALYZE SELECT * FROM people WHERE city = 'Beijing';
EXPLAIN ANALYZE SELECT * FROM people WHERE name = 'Tom';
EXPLAIN ANALYZE SELECT * FROM people WHERE age = 20;
EXPLAIN ANALYZE SELECT * FROM people WHERE name = 'Tom' and age = 20;
EXPLAIN ANALYZE SELECT * FROM people WHERE city = 'Beijing' and name = 'Tom';
EXPLAIN ANALYZE SELECT * FROM people WHERE city = 'Beijing' and age = 20;
分別執行一下,查看結果
Bitmap Heap Scan on people (cost=4.16..9.50 rows=2 width=244) (actual time=0.014..0.014 rows=3 loops=1)Recheck Cond: ((city)::text = 'Beijing'::text)Heap Blocks: exact=1-> Bitmap Index Scan on idx_city_name_age (cost=0.00..4.16 rows=2 width=0) (actual time=0.011..0.011 rows=3 loops=1)Index Cond: ((city)::text = 'Beijing'::text)
Planning Time: 0.056 ms
Execution Time: 0.031 ms-------------------------------------------------------------------------------
Seq Scan on people (cost=0.00..13.75 rows=2 width=244) (actual time=0.009..0.010 rows=2 loops=1)Filter: ((name)::text = 'Tom'::text)Rows Removed by Filter: 4
Planning Time: 0.060 ms
Execution Time: 0.020 ms-------------------------------------------------------------------------------
Seq Scan on people (cost=0.00..13.75 rows=2 width=244) (actual time=0.008..0.009 rows=1 loops=1)Filter: (age = 20)Rows Removed by Filter: 5
Planning Time: 0.054 ms
Execution Time: 0.017 ms-------------------------------------------------------------------------------
Seq Scan on people (cost=0.00..14.50 rows=1 width=244) (actual time=0.011..0.012 rows=1 loops=1)Filter: (((name)::text = 'Tom'::text) AND (age = 20))Rows Removed by Filter: 5
Planning Time: 0.056 ms
Execution Time: 0.020 ms-------------------------------------------------------------------------------
Index Scan using idx_city_name_age on people (cost=0.15..8.17 rows=1 width=244) (actual time=0.015..0.016 rows=2 loops=1)Index Cond: (((city)::text = 'Beijing'::text) AND ((name)::text = 'Tom'::text))
Planning Time: 0.057 ms
Execution Time: 0.026 ms-------------------------------------------------------------------------------
Index Scan using idx_city_name_age on people (cost=0.15..8.18 rows=1 width=244) (actual time=0.018..0.019 rows=1 loops=1)Index Cond: (((city)::text = 'Beijing'::text) AND (age = 20))
Planning Time: 0.062 ms
Execution Time: 0.031 ms
查看結果,我們發現,WHERE條件后面帶上 city
字段的 SQL 語句全部走了索引,其余字段全表掃描。
看到這兒,可能可以得出結論,PG 數據庫聯合索引遵循最左匹配原則,只有最左邊的字段存在才能命中索引。
這里才幾條數據,讓我們增加 people 表中的數據
INSERT INTO people (city, name, age)
SELECT-- 隨機分配 3 個城市CASE (random()*3)::intWHEN 0 THEN 'Beijing'WHEN 1 THEN 'Shanghai'ELSE 'Guangzhou'END,-- 隨機分配 10 個名字CASE (random()*10)::intWHEN 0 THEN 'Tom'WHEN 1 THEN 'Jerry'WHEN 2 THEN 'Alice'WHEN 3 THEN 'Bob'WHEN 4 THEN 'David'WHEN 5 THEN 'Eva'WHEN 6 THEN 'John'WHEN 7 THEN 'Lily'WHEN 8 THEN 'Lucy'ELSE 'Mike'END,(random()*100)::int -- 年齡 0~100
FROM generate_series(1, 100000);
這行 SQL 語句為 people 表增加了十萬條數據,讓我們再試試沒有 city
字段的查詢語句
EXPLAIN ANALYZE SELECT * FROM people WHERE age = 18 and name = 'David';
執行結果
Bitmap Heap Scan on people (cost=1556.38..1836.90 rows=107 width=22) (actual time=0.485..0.532 rows=80 loops=1)Recheck Cond: (((name)::text = 'David'::text) AND (age = 18))Heap Blocks: exact=78-> Bitmap Index Scan on idx_city_name_age (cost=0.00..1556.35 rows=107 width=0) (actual time=0.477..0.477 rows=80 loops=1)Index Cond: (((name)::text = 'David'::text) AND (age = 18))
Planning Time: 0.856 ms
Execution Time: 0.549 ms
我們看到沒有 city 字段,還是走了索引。但是似乎這種方式不是高效的方式,PG 數據庫綜合考慮還是走了索引,別的情況下可能不會走索引。
但是單獨的非最左索引字段肯定不走索引,例如:
EXPLAIN ANALYZE SELECT * FROM people WHERE age = 18;
EXPLAIN ANALYZE SELECT * FROM people WHERE name = 'David';
結果
Seq Scan on people (cost=0.00..1887.08 rows=1050 width=22) (actual time=0.009..6.824 rows=1039 loops=1)Filter: (age = 18)Rows Removed by Filter: 98967
Planning Time: 0.072 ms
Execution Time: 6.857 ms----------------------------------------------------------------
Seq Scan on people (cost=0.00..1887.08 rows=10204 width=22) (actual time=0.014..6.997 rows=10041 loops=1)Filter: ((name)::text = 'David'::text)Rows Removed by Filter: 89965
Planning Time: 0.057 ms
Execution Time: 7.207 ms
全表掃描,沒有走索引
總結
總的來說,Postgres 數據庫聯合索引生效條件遵循最左匹配原則。
在本例中,也就是說 city
字段存在于 where
條件的后面,才能高效的使用索引,如果沒有 city
字段,可能也會命中索引,但是是不高效的,當三個聯合索引字段都存在時,這時是最高效的查詢語句。
另外,查詢字段在 WHERE 條件后面的順序是不妨礙索引是否命中的,PG 優化器會識別優化。
你對 Postgre 數據庫的聯合索引有了解嗎?你是否知道何種情況下 PG 數據庫會高效的命中索引,可以留下你的評論,我們一起討論。
如有錯誤,請指正~