本篇內(nèi)容主要講解“PostgreSQL中的GIN索引有什么作用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“PostgreSQL中的GIN索引有什么作用”吧!
專注于為中小企業(yè)提供成都網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)榆社免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了1000+企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。GIN索引的主要用處是加快全文檢索full-text search的速度.
全文檢索
全文檢索full-text search的目的是從文檔集中找到匹配檢索條件的文檔(document).在搜索引擎中,如果有很多匹配的文檔,那么需要找到最匹配的那些,但在數(shù)據(jù)庫(kù)查詢中,找到滿足條件的即可.
在PG中,出于搜索的目的,文檔會(huì)被轉(zhuǎn)換為特定的類型tsvector,包含詞素(lexemes)和它們?cè)谖臋n中的位置.詞素Lexemes是那些轉(zhuǎn)換適合查詢的單詞形式(即分詞).比如:
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)
從本例可以看到,分詞后,出現(xiàn)了crook/man/mile和walk,其位置分別是4,10/5/11/8.同時(shí),也可以看到比如there等詞被忽略了,因?yàn)檫@些詞是stop words(從搜索引擎的角度來看,這些詞太過普通,不需要記錄),當(dāng)然這是可以配置的.
PG全文檢索中的查詢通過tsquery來表示,查詢條件包含1個(gè)或多個(gè)使用and(\&)/or(|)/not(!)等操作符連接的詞素.同樣的,使用括號(hào)來闡明操作的優(yōu)先級(jí).
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簡(jiǎn)介
GIN是Generalized Inverted Index通用倒排索引的簡(jiǎn)稱,如熟悉搜索引擎,這個(gè)概念不難理解.它所操作的數(shù)據(jù)類型的值由元素組成而不是原子的.這樣的數(shù)據(jù)類型成為復(fù)合數(shù)據(jù)類型.索引的是數(shù)據(jù)值中的元素.
舉個(gè)例子,比如書末尾的索引,它為每個(gè)術(shù)語提供了一個(gè)包含該術(shù)語出現(xiàn)位置所對(duì)應(yīng)的頁(yè)面列表。訪問方法(AM)需要確保索引元素的快速訪問,因此這些元素存儲(chǔ)在類似Btree中,引用包含復(fù)合值(內(nèi)含元素)數(shù)據(jù)行的有序集合鏈接到每個(gè)元素上.有序?qū)τ跀?shù)據(jù)檢索并不重要(如TIDs的排序),但對(duì)于索引的內(nèi)部結(jié)構(gòu)很重要.
元素不會(huì)從GIN索引中刪除,可能有人會(huì)認(rèn)為包含元素的值可以消失/新增/變化,但組成這些元素的元素集大多是穩(wěn)定的.這樣的處理方式大大簡(jiǎn)化了多進(jìn)程使用索引的算法.
如果TIDs不大,那么可以跟元素存儲(chǔ)在同一個(gè)page中(稱為posting list),但如果鏈表很大,會(huì)采用Btree這種更有效的數(shù)據(jù)結(jié)構(gòu),會(huì)存儲(chǔ)在分開的數(shù)據(jù)頁(yè)中(稱為posting tree).
因此,GIN索引包含含有元素的Btree,TIDs Btree或者普通鏈表會(huì)鏈接到該Btree的葉子行上.
與前面討論的GiST和SP-GiST索引一樣,GIN為應(yīng)用程序開發(fā)人員提供了接口,以支持復(fù)合數(shù)據(jù)類型上的各種操作。
舉個(gè)例子,下面是表ts,為ts創(chuàng)建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編號(hào) + page內(nèi)偏移)而不是箭頭來表示對(duì)TIDs的引用.
與常規(guī)的Btree不同,因?yàn)楸闅v只有一種方法,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)
在這個(gè)例子中,sheet/slit/slitter使用Btree存儲(chǔ)而其他元素則使用簡(jiǎn)單的鏈表.
如果我們希望知道元素的個(gè)數(shù),如何獲取?
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索引進(jìn)行掃描:
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)
執(zhí)行此查詢首先需要提取單個(gè)詞素(lexeme,亦即檢索鍵):mani/slitter.PG中有專門的API函數(shù)來完成,該函數(shù)考慮了由op class確定的數(shù)據(jù)類型和使用場(chǎng)景.
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中,下一步會(huì)同時(shí)檢索鍵并進(jìn)入TIDs鏈表中,得到:
mani — (0,2)
slitter — (0,1), (0,2), (1,2), (1,3), (2,2)
對(duì)于每一個(gè)找到的TID,調(diào)用consistency function API,由此函數(shù)確定找到的行是否匹配檢索鍵.因?yàn)椴樵優(yōu)锳ND,因此只返回(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
對(duì)GIN index的列進(jìn)行DML(主要是insert & update)是相當(dāng)慢的,每一個(gè)文檔通常包含許多需要索引的詞素.因此,雖然只添加或更新一個(gè)文檔,但也需要更新大量索引樹.換句話說,如果多個(gè)文檔同時(shí)更新,這些文檔中的詞素可能是一樣的,因此總的消耗可能比逐個(gè)更新文檔要小.
PG提供了fastupdate選項(xiàng),用打開此參數(shù)后,更新將在一個(gè)單獨(dú)的無序鏈表中處理,當(dāng)這個(gè)鏈表超過閾值(參數(shù):gin_pending_list_limit或索引同名存儲(chǔ)參數(shù))時(shí)才會(huì)對(duì)索引進(jìn)行更新.這種技術(shù)也有負(fù)面影響,一是降低了查詢效率(需額外掃描該鏈表),二是某個(gè)更新恰好碰上索引更新,那么該次更新會(huì)相對(duì)很久.
Limiting the query result
GIN AM的其中一個(gè)特性時(shí)通常會(huì)返回bitmap而不是逐個(gè)返回TID,因此執(zhí)行計(jì)劃都是bitmap scan.
這樣的特性胡導(dǎo)致LIMIT子句不會(huì)太有效:
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)
這是因?yàn)锽itmap Heap Scan的啟動(dòng)成本與Bitmap Index Scan不會(huì)差太多.
基于這樣的情況,PG提供了gin_fuzzy_search_limit參數(shù)控制返回的結(jié)果行數(shù)(默認(rèn)為0,即全部返回).
testdb=# show gin_fuzzy_search_limit ; gin_fuzzy_search_limit ------------------------ 0 (1 row)
到此,相信大家對(duì)“PostgreSQL中的GIN索引有什么作用”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!