PostgreSQL DBA(51) - Index(GIN)
本節簡單介紹了PostgreSQL中的GIN索引,包括GIN索引的基礎知識和結構等.GIN索引的主要用處是加快全文檢索full-text search的速度.
全文檢索
全文檢索full-text search的目的是從文件集中找到匹配檢索條件的文件(document).在搜尋引擎中,如果有很多匹配的文件,那麼需要找到最匹配的那些,但在資料庫查詢中,找到滿足條件的即可.
在PG中,出於搜尋的目的,文件會被轉換為特定的型別 tsvector ,包含詞素(lexemes)和它們在文件中的位置.詞素Lexemes是那些轉換適合查詢的單詞形式(即分詞).比如:
testdb=# select to_tsvector('There was a crooked man, and he walked a crooked mile');
to_tsvector
-----------------------------------------
'crook':4,10 'man':5 'mile':11 'walk':8
(1 row)
從本例可以看到,分詞後,出現了crook/man/mile和walk,其位置分別是4,10/5/11/8.同時,也可以看到比如there等詞被忽略了,因為這些詞是stop words(從搜尋引擎的角度來看,這些詞太過普通,不需要記錄),當然這是可以配置的.
PG全文檢索中的查詢通過tsquery來表示,查詢條件包含1個或多個使用and(\&)/or(|)/not(!)等操作符連線的詞素.同樣的,使用括號來闡明操作的優先順序.
testdb=# select to_tsquery('man & (walking | running)');
to_tsquery
----------------------------
'man' & ( 'walk' | 'run' )
(1 row)
操作符 @@ 用於全文檢索
testdb=# select to_tsvector('There was a crooked man, and he walked a crooked mile') @@ to_tsquery('man & (walking | running)');
?column?
----------
t
(1 row)
select to_tsvector('There was a crooked man, and he walked a crooked mile') @@ to_tsquery('man & (going | running)');
?column?
----------
f
(1 row)
GIN簡介
GIN是Generalized Inverted Index通用倒排索引的簡稱,如熟悉搜尋引擎,這個概念不難理解.它所操作的資料型別的值由元素組成而不是原子的.這樣的資料型別成為複合資料型別.索引的是資料值中的元素.
舉個例子,比如書末尾的索引,它為每個術語提供了一個包含該術語出現位置所對應的頁面列表。訪問方法(AM)需要確保索引元素的快速訪問,因此這些元素儲存在類似Btree中,引用包含複合值(內含元素)資料行的有序集合連結到每個元素上.有序對於資料檢索並不重要(如TIDs的排序),但對於索引的內部結構很重要.
元素不會從GIN索引中刪除,可能有人會認為包含元素的值可以消失/新增/變化,但組成這些元素的元素集大多是穩定的.這樣的處理方式大大簡化了多程式使用索引的演算法.
如果TIDs不大,那麼可以跟元素儲存在同一個page中(
稱為posting list
),但如果連結串列很大,會採用Btree這種更有效的資料結構,會儲存在分開的資料頁中(
稱為posting tree
).
因此,GIN索引包含含有元素的Btree,TIDs Btree或者普通連結串列會連結到該Btree的葉子行上.
與前面討論的GiST和SP-GiST索引一樣,GIN為應用程式開發人員提供了介面,以支援複合資料型別上的各種操作。
舉個例子,下面是表ts,為ts建立GIN索引:
testdb=# drop table if exists ts;
psql: NOTICE: table "ts" does not exist, skipping
DROP TABLE
testdb=# create table ts(doc text, doc_tsv tsvector);
CREATE TABLE
testdb=# truncate table ts;
slitter.'),
('I am a sheet slitter.'),
('I slit sheets.'),
('I am the sleekest sheet slitter that ever slit sheets.'),
('She slits the sheet she sits on.');
update ts set doc_tsv = to_tsvector(doc);
create index on ts using gin(doc_tsv);
TRUNCATE TABLE
testdb=# insert into ts(doc) values
testdb-# ('Can a sheet slitter slit sheets?'),
testdb-# ('How many sheets could a sheet slitter slit?'),
testdb-# ('I slit a sheet, a sheet I slit.'),
testdb-# ('Upon a slitted sheet I sit.'),
testdb-# ('Whoever slit the sheets is a good sheet slitter.'),
testdb-# ('I am a sheet slitter.'),
testdb-# ('I slit sheets.'),
testdb-# ('I am the sleekest sheet slitter that ever slit sheets.'),
testdb-# ('She slits the sheet she sits on.');
INSERT 0 9
testdb=#
testdb=# update ts set doc_tsv = to_tsvector(doc);
UPDATE 9
testdb=#
testdb=# create index on ts using gin(doc_tsv);
CREATE INDEX
其可能的索引結構如下圖所示:
在這裡,使用黑底(page編號 + page內偏移)而不是箭頭來表示對TIDs的引用.
與常規的Btree不同,因為遍歷只有一種方法,GIN索引由單向連結串列連線,而不是雙向連結串列.
testdb=# select ctid, left(doc,20), doc_tsv from ts;
ctid | left | doc_tsv
--------+----------------------+---------------------------------------------------------
(0,10) | Can a sheet slitter | 'sheet':3,6 'slit':5 'slitter':4
(0,11) | How many sheets coul | 'could':4 'mani':2 'sheet':3,6 'slit':8 'slitter':7
(0,12) | I slit a sheet, a sh | 'sheet':4,6 'slit':2,8
(0,13) | Upon a slitted sheet | 'sheet':4 'sit':6 'slit':3 'upon':1
(0,14) | Whoever slit the she | 'good':7 'sheet':4,8 'slit':2 'slitter':9 'whoever':1
(0,15) | I am a sheet slitter | 'sheet':4 'slitter':5
(0,16) | I slit sheets. | 'sheet':3 'slit':2
(0,17) | I am the sleekest sh | 'ever':8 'sheet':5,10 'sleekest':4 'slit':9 'slitter':6
(0,18) | She slits the sheet | 'sheet':4 'sit':6 'slit':2
(9 rows)
在這個例子中,sheet/slit/slitter使用Btree儲存而其他元素則使用簡單的連結串列.
如果我們希望知道元素的個數,如何獲取?
testdb=# select (unnest(doc_tsv)).lexeme, count(*) from ts
testdb-# group by 1 order by 2 desc;
lexeme | count
----------+-------
sheet | 9
slit | 8
slitter | 5
sit | 2
upon | 1
mani | 1
whoever | 1
sleekest | 1
good | 1
could | 1
ever | 1
(11 rows)
下面舉例說明如何通過GIN索引進行掃描:
testdb=# explain(costs off)
testdb-# select doc from ts where doc_tsv @@ to_tsquery('many & slitter');
QUERY PLAN
-----------------------------------------------------------
Seq Scan on ts
Filter: (doc_tsv @@ to_tsquery('many & slitter'::text))
(2 rows)
testdb=# set enable_seqscan=off;
SET
testdb=# explain(costs off)
select doc from ts where doc_tsv @@ to_tsquery('many & slitter');
QUERY PLAN
---------------------------------------------------------------------
Bitmap Heap Scan on ts
Recheck Cond: (doc_tsv @@ to_tsquery('many & slitter'::text))
-> Bitmap Index Scan on ts_doc_tsv_idx
Index Cond: (doc_tsv @@ to_tsquery('many & slitter'::text))
(4 rows)
執行此查詢首先需要提取單個詞素(lexeme,亦即檢索鍵):mani/slitter.PG中有專門的API函式來完成,該函式考慮了由op class確定的資料型別和使用場景.
testdb=# select amop.amopopr::regoperator, amop.amopstrategy
testdb-# from pg_opclass opc, pg_opfamily opf, pg_am am, pg_amop amop
testdb-# where opc.opcname = 'tsvector_ops'
testdb-# and opf.oid = opc.opcfamily
testdb-# and am.oid = opf.opfmethod
testdb-# and amop.amopfamily = opc.opcfamily
testdb-# and am.amname = 'gin'
testdb-# and amop.amoplefttype = opc.opcintype;
amopopr | amopstrategy
-----------------------+--------------
@@(tsvector,tsquery) | 1
@@@(tsvector,tsquery) | 2
(2 rows)
回到本例中,在詞素Btree中,下一步會同時檢索鍵並進入TIDs連結串列中,得到:
mani — (0,2)
slitter — (0,1), (0,2), (1,2), (1,3), (2,2)
詳見下圖:
對於每一個找到的TID,呼叫 consistency function API,由此函式確定找到的行是否匹配檢索鍵.因為查詢為AND,因此只返回(0,2).
testdb=# select doc from ts where doc_tsv @@ to_tsquery('many & slitter');
doc
---------------------------------------------
How many sheets could a sheet slitter slit?
(1 row)
Slow Update
對GIN index的列進行DML(主要是insert & update)是相當慢的,每一個文件通常包含許多需要索引的詞素.因此,雖然只新增或更新一個文件,但也需要更新大量索引樹.換句話說,如果多個文件同時更新,這些文件中的詞素可能是一樣的,因此總的消耗可能比逐個更新文件要小.
PG提供了fastupdate選項,用開啟此引數後,更新將在一個單獨的無序連結串列中處理,當這個連結串列超過閾值(引數:gin_pending_list_limit或索引同名儲存引數)時才會對索引進行更新.這種技術也有負面影響,一是降低了查詢效率(需額外掃描該連結串列),二是某個更新恰好碰上索引更新,那麼該次更新會相對很久.
Limiting the query result
GIN AM的其中一個特性時通常會返回bitmap而不是逐個返回TID,因此執行計劃都是bitmap scan.
這樣的特性胡導致LIMIT子句不會太有效:
testdb=# explain verbose
select doc from ts where doc_tsv @@ to_tsquery('many & slitter');
QUERY PLAN
------------------------------------------------------------------------------
Bitmap Heap Scan on public.ts (cost=12.25..16.51 rows=1 width=32)
Output: doc
Recheck Cond: (ts.doc_tsv @@ to_tsquery('many & slitter'::text))
-> Bitmap Index Scan on ts_doc_tsv_idx (cost=0.00..12.25 rows=1 width=0)
Index Cond: (ts.doc_tsv @@ to_tsquery('many & slitter'::text))
(5 rows)
testdb=# explain verbose
select doc from ts where doc_tsv @@ to_tsquery('many & slitter') limit 1;
QUERY PLAN
------------------------------------------------------------------------------------
Limit (cost=12.25..16.51 rows=1 width=32)
Output: doc
-> Bitmap Heap Scan on public.ts (cost=12.25..16.51 rows=1 width=32)
Output: doc
Recheck Cond: (ts.doc_tsv @@ to_tsquery('many & slitter'::text))
-> Bitmap Index Scan on ts_doc_tsv_idx (cost=0.00..12.25 rows=1 width=0)
Index Cond: (ts.doc_tsv @@ to_tsquery('many & slitter'::text))
(7 rows)
這是因為Bitmap Heap Scan的啟動成本與Bitmap Index Scan不會差太多.
基於這樣的情況,PG提供了gin_fuzzy_search_limit引數控制返回的結果行數(預設為0,即全部返回).
testdb=# show gin_fuzzy_search_limit ;
gin_fuzzy_search_limit
------------------------
0
(1 row)
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2649935/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PostgreSQL DBA(59) - Index(Bloom)SQLIndexOOM
- PostgreSQL DBA(52) - Index(RUM)SQLIndex
- PostgreSQL DBA(47) - Index(Btree)SQLIndex
- PostgreSQL DBA(48) - Index(GiST)SQLIndex
- PostgreSQL DBA(43) - Index(Hash)SQLIndex
- PostgreSQL DBA(53) - Index(BRIN)SQLIndex
- PostgreSQL DBA(41) - PG Index PropertiesSQLIndex
- PostgreSQL DBA(170) - pgAdmin(Queries For Index Maintenance)SQLIndexAINaN
- PostgreSQL DBA(49) - Index(SP-GiST)SQLIndex
- PostgreSQL DBA(119) - pgAdmin(LIMIT:Index Scan vs Bitmap Index Scan)SQLMITIndex
- PostgreSQL DBA(177) - Serializability Isolation(Index vs NonIndex)SQLIndex
- PostgreSQL DBA(139) - PG 12(B-tree index improvement 1#)SQLIndex
- PostgreSQL:INDEXSQLIndex
- PostgreSQL DBA(45) - Hypothetical Indexes in PostgreSQLSQLIndex
- PostgreSQL DBA(58) - DBLinkSQL
- PostgreSQL DBA(35) - CTESQL
- PostgreSQL DBA(42) - localeSQL
- PostgreSQL DBA(191) - CollationSQL
- PostgreSQL DBA(185) - watchSQL
- PostgreSQL DBA(182) - HOTSQL
- PostgreSQL DBA(133) - Extension(postgresql_anonymizer)SQL
- PostgreSQL DBA(181) - Using PostgreSQL as a Data WarehouseSQL
- 【INDEX】Postgresql索引介紹IndexSQL索引
- PostgreSQL DBA(91) - PG upgradeSQL
- PostgreSQL DBA(150) - Extension(pgmetrics)SQL
- PostgreSQL DBA(187) - TCP keepaliveSQLTCP
- PostgreSQL DBA(186) - SQL Group BySQL
- PostgreSQL DBA(184) - Extension(hypoPG)SQL
- PostgreSQL DBA(142) - PG 12(Monitoring PostgreSQL VACUUM processes)SQL
- PostgreSQL DBA(7) - pgbench簡介SQL
- PostgreSQL DBA(8) - sysbench簡介SQL
- PostgreSQL DBA(136) - Develop(Common Mistakes)SQLdev
- PostgreSQL DBA(125) - PG 12(TPCC)SQL
- PostgreSQL DBA(122) - Develop(EDB package)SQLdevPackage
- PostgreSQL DBA(120) - pgAdmin(HA with Patroni)SQL
- PostgreSQL DBA(86) - Table Parameter(fillfactor)SQL
- PostgreSQL DBA(87) - benchmarksql簡介SQL
- PostgreSQL DBA(80) - Object Identifier TypesSQLObjectIDE