selectlab.prove('with jsonb_path_ops, the index reads about 121 pages for the same query',lab.node_pages($$selectidfromeventswherepayload@>'{"duration_ms": 4242, "region": "syd"}'$$,'Bitmap Index Scan')between90and160);
selectlab.prove('the default jsonb_ops GIN index is about 20 MB',pg_relation_size('events_payload_gin')between18e6and23e6);selectlab.prove('with jsonb_ops, the index alone reads about 987 pages for 81 rows',lab.node_pages($$selectidfromeventswherepayload@>'{"duration_ms": 4242, "region": "syd"}'$$,'Bitmap Index Scan')between850and1150);selectlab.prove('the jsonb_path_ops index is about 14 MB',pg_relation_size('events_payload_pathops')between12.5e6and16e6);
selectlab.prove('those rows sit on about 33,343 of the 35,173 heap pages (over 90 percent)',lab.node_sum($$selectcount(*)fromeventswherepayload@>'{"status": "failed", "region": "syd"}'$$,'Bitmap Heap Scan','Exact Heap Blocks')>0.9*(selectrelpagesfrompg_classwhererelname='events'));
altertableprojectsaddcolumntagstext[]notnulldefault'{}';-- 1 to 4 random tags per project from a list of 20, plus 'gdpr' on 50 projects
-- (the lab has the exact update)
createindexprojects_tags_idxonprojectsusinggin(tags);
Bitmap Heap Scan on projects (actual ... rows=50.00 loops=1)
Heap Blocks: exact=50
Buffers: shared hit=53
-> Bitmap Index Scan on projects_tags_idx (actual ... rows=50.00 loops=1)
Buffers: shared hit=3
先读取 3 个索引页,再为每个带标签的项目读取一个堆页。使用 = any 的写法时:
Seq Scan on projects
Filter: ('gdpr'::text = ANY (tags))
动手验证 · lab 17-more-indexes
selectlab.prove('with a GIN index on tags, the index reads 3 pages and the heap one page per match: 53 in all',lab.node_pages($$selectidfromprojectswheretags@>'{gdpr}'$$,'Bitmap Index Scan')<=4andlab.buffers($$selectidfromprojectswheretags@>'{gdpr}'$$)between45and56);selectlab.prove('''gdpr'' = any (tags) cannot use the GIN index: a sequential scan',lab.nodes($$selectidfromprojectswhere'gdpr'=any(tags)$$)='{Seq Scan}');
createtablecomments(idbigintgeneratedalwaysasidentityprimarykey,event_idbigintnotnullreferencesevents(id),bodytextnotnull,searchtsvectorgeneratedalwaysas(to_tsvector('english',body))stored);-- one per comment event: 249,934 comments of 6 to 15 words,
-- 49 of them mention "deadlock" (the lab has the generator)
createindexcomments_search_idxoncommentsusinggin(search);
Bitmap Heap Scan on comments (actual ... rows=49.00 loops=1)
Heap Blocks: exact=49
Buffers: shared hit=27 read=24
-> Bitmap Index Scan on comments_search_idx (actual ... rows=49.00 loops=1)
Buffers: shared hit=2
selectlab.prove('without an index, a search reads every page of comments (about 6,700)',lab.buffers($$selectid,bodyfromcommentswheresearch@@to_tsquery('english','deadlock')$$)between6400and7000);createindexcomments_search_idxoncommentsusinggin(search);selectlab.prove('with a GIN index, the search finds all 49 and the index reads under 5 pages',lab.node_sum($$selectid,bodyfromcommentswheresearch@@to_tsquery('english','deadlock')$$,'Bitmap Heap Scan','Actual Rows')=49andlab.node_pages($$selectid,bodyfromcommentswheresearch@@to_tsquery('english','deadlock')$$,'Bitmap Index Scan')<5);selectlab.prove('the full-text index is about 2.4 MB for a quarter-million comments',pg_relation_size('comments_search_idx')between2.0e6and3.0e6);
selectlab.prove('after 5,000 inserts the pending list holds 5,000 tuples on a few dozen pages',(selectpending_tuples=5000andpending_pagesbetween25and60frompgstatginindex('events_payload_pathops')));selectlab.prove('every search now scans the pending pages too (121 index pages became about 158)',lab.node_pages($$selectidfromeventswherepayload@>'{"duration_ms": 4242, "region": "syd"}'$$,'Bitmap Index Scan')-:pages_before>=(selectpending_pages-2frompgstatginindex('events_payload_pathops')));
B-树从字符串开头开始排序,因此它最多服务类似 like 'user42%' 的前缀查询(而且还需要正确的操作符类,下一节会说明),永远不能服务 like '%r4242@%'。pg_trgm 扩展把字符串拆成三字符组(trigrams),即每三个字符组成的序列(show_trgm('user4242')),从而让子串搜索能够使用索引:
即使 like 'user4242@%' 也不能使用 email 上的普通 B-树。在这个数据库的 en_US.utf8 这样的非 C 排序规则下,B-树顺序不是字节顺序,因此前缀不一定对应一个连续范围。使用 text_pattern_ops(按字节排序)建索引,前缀就变成了一个范围:
Index Scan using users_email_pattern on users (actual ... rows=1.00 loops=1)
Index Cond: ((email ~>=~ 'user4242@'::text) AND (email ~<~ 'user4242A'::text))
这个索引不能在数据库排序规则下服务 <、> 或 order by。两种需求都要?那就两个索引都建,或者在列上使用 C 排序规则。
selectlab.prove('trigram GiST on email is about 11 MB, over 2.5 times the GIN',pg_relation_size('users_email_trgm_gist')between10e6and13.5e6andpg_relation_size('users_email_trgm_gist')::numeric/pg_relation_size('users_email_trgm_gin')>2.5);selectlab.prove('with GiST, the similarity search is an index scan ordered by distance',lab.nodes($$selectemailfromusersorderbyemail<->'usr4242@exmaple.com'limit5$$)='{Limit,Index Scan}');
do$$begininsertintodeploy_freezes(project_id,during,reason)values(4242,tstzrange('2026-12-31','2027-01-02'),'new year');raiseexception'NOT PROVED: the overlapping freeze was accepted';exceptionwhenexclusion_violationthenperformset_config('lab.freeze_rejected','yes',false);end$$;selectlab.prove('an overlapping freeze for the same project is rejected (exclusion_violation)',current_setting('lab.freeze_rejected',true)='yes');selectlab.prove('the constraint''s index, led by project_id, reads more pages than the sequential scan',lab.buffers($$selectproject_id,reasonfromdeploy_freezeswhereduring@>now()-interval'100 days'$$)>:seq_pages);createindexdeploy_freezes_during_idxondeploy_freezesusinggist(during);selectlab.prove('a GiST index on the range alone answers in about 7 index pages',lab.node_pages($$selectproject_id,reasonfromdeploy_freezeswhereduring@>now()-interval'100 days'$$,'Bitmap Index Scan')<20);
selectlab.prove('with BRIN (128 pages per range), the one-day summary reads about 261 pages',lab.buffers(:'day_query')between200and320);selectlab.prove('BRIN at 32 pages per range gets within 20 pages of the B-tree',:brin32_pages-:btree_pages<=20);selectlab.prove('the B-tree is over 1,000 times larger than the BRIN (about 1,800)',pg_relation_size('events_created_btree')::numeric/pg_relation_size('events_created_brin')>1000);selectlab.prove('the BRIN node''s "rows" is ten per matching page, not a row count',lab.node_sum(:'day_query','Bitmap Index Scan','Actual Rows')=10*lab.node_sum(:'day_query','Bitmap Heap Scan','Lossy Heap Blocks'));
selectlab.prove('on the long key, the hash index is about a quarter of the B-tree',pg_relation_size('users_api_key_hash')::numeric/pg_relation_size('users_api_key_btree')between0.2and0.3);selectlab.prove('the hash index size does not depend on key width: same size on email',pg_relation_size('users_email_hash')=pg_relation_size('users_api_key_hash'));selectlab.prove('a hash lookup on api_key touches 3 pages (metapage, bucket, heap)',lab.buffers($$selectidfromuserswhereapi_key=encode(sha512('key4242'::bytea),'hex')$$)=3);