CodeGym /課程 /SQL SELF /陣列索引與運算子(`@>`、`<@`、`&&`)快速搜尋

陣列索引與運算子(`@>`、`<@`、`&&`)快速搜尋

SQL SELF
等級 38 , 課堂 1
開放

在 PostgreSQL 裡,陣列可以讓你在一個資料表欄位裡存一堆值。這超方便,像是你要把一篇文章的標籤(tags)或產品的分類(categories)放一起的時候。 但只要你開始搜尋、篩選或交集陣列,效能就可能掉到谷底。所以這時候,陣列索引就像救星一樣。索引可以加速這些操作,例如:

  • 檢查陣列有沒有某個元素,
  • 找出包含指定元素的陣列,
  • 檢查陣列有沒有交集。

操作陣列的運算子

在我們深入索引之前,先來搞懂幾個操作陣列的主要運算子:

@>(contains) — 檢查一個陣列是不是包含另一個陣列的所有元素。

SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];

這裡我們在找有 "SQL" 這個標籤的課程。

<@(is contained by) — 檢查一個陣列是不是被另一個陣列包含。

SELECT *
FROM courses
WHERE ARRAY['PostgreSQL', 'SQL'] <@ tags;

這裡我們找的是標籤包含 ARRAY['PostgreSQL', 'SQL'] 裡所有元素的課程。

&&(overlap) — 檢查兩個陣列有沒有交集。

SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];

這個查詢會找到有 "NoSQL" 或 "Big Data" 其中一個標籤的課程。

索引怎麼幫忙?

想像一下,你有一個 courses 表,裡面有幾百萬筆資料,你用上面那些運算子查詢。沒有索引的話,PostgreSQL 只能一筆一筆慢慢掃——這會慢到讓你懷疑人生(尤其是你等 compile 的時候)。 有了索引就不一樣了。PostgreSQL 有兩種適合陣列的索引:

  1. GIN(Generalized Inverted Index) — 陣列的首選。
  2. BTREE — 用來整個陣列比大小。

範例:建立陣列索引

我們來建個小表,實際測試一下。

CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    tags TEXT[] NOT NULL
);

加幾筆資料:

INSERT INTO courses (name, tags)
VALUES
    ('SQL 基礎', ARRAY['SQL', 'PostgreSQL', '資料庫']),
    ('Big Data 實戰', ARRAY['Hadoop', 'Big Data', 'NoSQL']),
    ('Python 開發', ARRAY['Python', 'Web', '資料']),
    ('PostgreSQL 課程', ARRAY['PostgreSQL', 'Advanced', 'SQL']);

這張表大概長這樣:

id name tags
1 SQL 基礎 {SQL, PostgreSQL, 資料庫}
2 Big Data 實戰 {Hadoop, Big Data, NoSQL}
3 Python 開發 {Python, Web, 資料}
4 PostgreSQL 課程 {PostgreSQL, Advanced, SQL}

沒索引:超慢搜尋

現在假設我們要找所有有 SQL 標籤的課程。

EXPLAIN ANALYZE
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];

這查詢雖然能跑,但資料一多就會慢到爆。PostgreSQL 會做 Sequential Scan(逐筆掃描),每一行都要檢查。

查詢結果大概會是:

id name tags
1 SQL 基礎 {SQL, PostgreSQL, 資料庫}
4 PostgreSQL 課程 {PostgreSQL, Advanced, SQL}

建立 GIN 索引

為了加速搜尋,我們來建個 GIN 索引:

CREATE INDEX idx_courses_tags
ON courses USING GIN (tags);

再跑一次同樣的查詢:

EXPLAIN ANALYZE
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];

這次 PostgreSQL 會用我們剛剛建的 GIN 索引,查詢速度快超多。

原本是 Sequential Scan,現在執行計畫會看到 Bitmap Index Scan

Step Rows Cost Info
Bitmap Index Scan N 用索引 idx_courses_tags
Bitmap Heap Scan N 從表裡挑出資料

RowsCost 會依資料量不同,但重點是執行計畫裡會用到索引。

運算子跟索引怎麼配合?

範例 1:@> 運算子

查詢:

SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];

GIN 索引超適合這個運算子。Postgres 會很快找出哪些資料有這個元素,直接回傳結果。

查詢結果:

id name tags
1 SQL 基礎 {SQL, PostgreSQL, 資料庫}
4 PostgreSQL 課程 {PostgreSQL, Advanced, SQL}

@> 就是 "包含" 的意思——這查詢會回傳所有 tags 陣列裡 SQL 的課程。

範例 2:&& 運算子

查詢:

SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];

這個運算子會檢查陣列有沒有交集:只要 tags 裡有傳進來陣列的任一元素就會回傳。

GIN 索引又發威了——資料再多也能快快找到。

查詢結果:

id name tags
2 Big Data 實戰 {Hadoop, Big Data, NoSQL}
&&

意思是 "有交集"——只要 有一個 標籤對得上就成立。

索引與優化建議

用陣列時,記得這幾點:

  1. 搜尋陣列內容就用 GIN 索引。比逐筆掃描快很多。
  2. 只在常查詢的欄位加索引。索引會佔空間、寫入會慢,不要亂加一通。
  3. EXPLAINEXPLAIN ANALYZE 來 profile 查詢,確定你的索引真的有被用到。

範例:為陣列建立索引

來看看怎麼針對不同操作建立索引,還有實際意義。

@> 運算子的索引

假設我們已經有這張 courses 表:

id name tags
1 SQL 基礎 {SQL, PostgreSQL, 資料庫}
2 Big Data 實戰 {Hadoop, Big Data, NoSQL}
3 Python 開發 {Python, Web, 資料}
4 PostgreSQL 課程 {PostgreSQL, Advanced, SQL}

要加速 @>(陣列包含元素)查詢,建個 GIN 索引:

CREATE INDEX idx_courses_tags_gin
ON courses USING GIN (tags);

然後查詢:

SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];

結果:

id name tags
1 SQL 基礎 {SQL, PostgreSQL, 資料庫}
4 PostgreSQL 課程 {PostgreSQL, Advanced, SQL}

@><@&& 的索引

表還是跟剛剛一樣。

因為 @><@&& 都能用 GIN 索引加速,所以直接建一個通用索引就好:

CREATE INDEX idx_tags
ON courses USING GIN (tags);

查詢範例跟結果:

  • @> — 檢查陣列有沒有指定元素:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
id name tags
1 SQL 基礎 {SQL, PostgreSQL, 資料庫}
4 PostgreSQL 課程 {PostgreSQL, Advanced, SQL}

  • <@ — 檢查陣列是不是被另一個陣列包含:
SELECT *
FROM courses
WHERE tags <@ ARRAY['SQL', 'PostgreSQL', 'Advanced', 'Big Data', 'NoSQL', 'Python'];
id name tags
1 SQL 基礎 {SQL, PostgreSQL, 資料庫}
2 Big Data 實戰 {Hadoop, Big Data, NoSQL}
3 Python 開發 {Python, Web, 資料}
4 PostgreSQL 課程 {PostgreSQL, Advanced, SQL}

  • && — 檢查陣列有沒有交集:
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
id name tags
2 Big Data 實戰 {Hadoop, Big Data, NoSQL}

來點進階的

寫個查詢,找出標籤跟 ['Python', 'SQL', 'NoSQL'] 至少有一個交集的課程:

SELECT *
FROM courses
WHERE tags && ARRAY['Python', 'SQL', 'NoSQL'];

結果:

id name tags
1 SQL 基礎 {SQL,PostgreSQL,資料庫}
2 Big Data 實戰 {Hadoop,Big Data,NoSQL}
3 Python 開發 {Python,Web,資料}

GIN 索引的話,這種查詢就算資料表有幾百萬筆也能瞬間回傳。

用陣列常見的錯誤

索引沒被用到:如果你在 EXPLAIN 裡看到 Seq Scan,檢查一下索引有沒有建好,還有你用的運算子是不是支援索引。

很少用到的陣列:如果這個欄位很少查詢或常更新,索引只會佔空間,沒什麼幫助。

索引太多:索引會吃硬碟、寫入會慢,所以只建真的會用到的索引。

現在你已經有所有 PostgreSQL 陣列操作的神兵利器了——用 @><@&&GIN 索引讓查詢飛起來。快去自己資料試試看吧!

2
任務
SQL SELF, 等級 38, 課堂 1
上鎖
建立含有陣列的資料表並用 `@>` 基本查詢
建立含有陣列的資料表並用 `@>` 基本查詢
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION