ინდექსი არის ცხრილის ნაწილის მეორე, დახარისხებული ასლი, რომელიც ყოველ ჩაწერაზე განახლდება და PostgreSQL-ს საშუალებას აძლევს, სტრიქონები ყველას წაკითხვის გარეშე იპოვოს. ამ წინადადებაში გარიგების ორივე მხარეა: წაკითხვა უფრო სწრაფდება, ჩაწერა ნელდება და დისკი იხარჯება. ნელი მონაცემთა ბაზების უმეტესობას აკლია ორი-სამი ინდექსი; ჩაწერაზე ნელი მონაცემთა ბაზების უმეტესობას კი ცხრილზე რვა ინდექსი აქვს, მაშინ როცა სამი სჭირდებოდა.
პრაქტიკული შეჯამება, თუ მხოლოდ ერთი რამ გახსოვს: query-სთვის WHERE tenant_id = $1 AND status = 'open' ORDER BY created_at DESC LIMIT 20 საჭირო ინდექსი ერთი composite B-tree-ია (tenant_id, status, created_at DESC)-ზე და არა სამი ცალკე ინდექსი სამ სვეტზე. composite ინდექსში სვეტების თანმიმდევრობა დეკორატიული არ არის; ის განსაზღვრავს, შეიძლება თუ არა ინდექსის გამოყენება საერთოდ.
რა არის ინდექსი და რას პასუხობს B-tree#
ნაგულისხმევი ინდექსის ტიპი, და სწორი პასუხი ალბათ ათიდან ცხრა შემთხვევაში, B-tree-ია. ის ინდექსირებულ მნიშვნელობებს დახარისხებულად ინახავს დაბალანსებულ ხეში, ამიტომ PostgreSQL-ს შეუძლია ფესვიდან ფოთლამდე რამდენიმე გვერდის წაკითხვით მივიდეს და შემდეგ შესაბამის ჩანაწერებზე გვერდზე გაიაროს.
რადგან ჩანაწერები დახარისხებულია, ერთი B-tree ერთსა და იმავე სვეტზე კითხვების ფართო სპექტრს პასუხობს:
- ტოლობა:
WHERE email = 'a@example.com' - დიაპაზონები:
WHERE created_at >= now() - interval '7 days' BETWEENდაINსიებიIS NULLდაIS NOT NULL- PostgreSQL null-ებს ინდექსირებს- დახარისხება:
ORDER BY created_at DESCLIMIT-ით, უკან წაკითხული - პრეფიქსით ძებნა
LIKE 'inv-2026%'-ით, მაგრამ მხოლოდ თუ მონაცემთა ბაზის collationC-ა ან ინდექსიtext_pattern_ops-ითაა გამოცხადებული
ეს ბოლო ყველა ლოკალში, გარდა C-სა, ხალხს ეჭერება. თუ პრეფიქსით ძებნა მნიშვნელოვანია, დაამატე ოპერატორის კლასი ცხადად:
CREATE INDEX orders_ref_prefix ON app.orders (reference text_pattern_ops);B-tree ვერ დაგეხმარება წინა wildcard-ში (LIKE '%carrot%'), query-ში სვეტზე გამოყენებულ ფუნქციაში ან შედარებაში, რომლის ტიპიც ინდექსს არ ემთხვევა. მეორე ყველაზე გავრცელებულია:
-- Does not use an index on created_at: the column is wrapped in a castWHERE created_at::date = '2026-09-01'-- Does, because the column is left aloneWHERE created_at >= '2026-09-01' AND created_at < '2026-09-02'როცა query ნელია და სვეტს "აშკარად აქვს ინდექსი", შეამოწმე, ხომ არ იყენებს query ამ სვეტზე ფუნქციას ან cast-ს. თუ უნდა იყენებდეს, ინდექსირე გამოსახულება, რაც ქვემოთაა განხილული.
Composite ინდექსები და სვეტების თანმიმდევრობა#
composite ინდექსი დახარისხებულია ჯერ პირველი სვეტით, შემდეგ მასში მეორეთი და ასე შემდეგ - ისევე, როგორც სატელეფონო წიგნი დახარისხებულია ჯერ გვარით, შემდეგ სახელით. ეს მას იძლევა უკიდურესი მარცხენა პრეფიქსის წესს: ინდექსს (a, b, c)-ზე შეუძლია query-ს მოემსახუროს, რომელიც ფილტრავს a-ით, a-სა და b-ით, ან სამივეთი. ის თითქმის უსარგებლოა query-სთვის, რომელიც მხოლოდ b-ით ფილტრავს.
ორი წესი რეალური შემთხვევების უმეტესობას ფარავს:
- ჯერ ტოლობის სვეტები, ბოლოს დიაპაზონის ან დახარისხების სვეტი. ინდექსით
(tenant_id, created_at)query, რომელიც ფილტრავსtenant_id = 7-ით და ხარისხობსcreated_at-ით, პირდაპირ სწორ ნაჭერთან მიდის და თანმიმდევრობით კითხულობს. სვეტები შეაბრუნე და ვეღარ შეძლებს. - ერთი composite სჯობს რამდენიმე ერთსვეტიან ინდექსს, როცა სვეტები ერთად ჩანს. PostgreSQL-ს ორი ინდექსის გაერთიანება bitmap scan-ით შეუძლია, მაგრამ ეს დამატებით ნაბიჯს ჯდება და heap-ში სტრიქონებს ხელახლა ამოწმებს; ერთი ინდექსი, რომელიც query-ს ემთხვევა, ყოველთვის უფრო იაფია.
დამუშავებული მაგალითი:
-- The query the application runs a thousand times an hourSELECT id, total, created_atFROM app.ordersWHERE tenant_id = $1 AND status = 'open'ORDER BY created_at DESCLIMIT 20;-- The index it wantsCREATE INDEX orders_tenant_status_created ON app.orders (tenant_id, status, created_at DESC);ამ ინდექსით გეგმა არის index scan, რომელიც ოცი სტრიქონის შემდეგ ჩერდება. მის გარეშე PostgreSQL ამ tenant-ის ყველა შეკვეთას კითხულობს, ყველას ხარისხობს და მეოცის შემდეგ ყველაფერს აგდებს - სამუშაო, რომელიც ცხრილთან ერთად იზრდება, მაშინ როცა შედეგი იგივე ზომისაა. განსაზღვრებაში DESC ერთსვეტიანი დახარისხებისთვის არჩევითია (B-tree უკან შეიძლება წაიკითხოს), მაგრამ მნიშვნელოვანია, როცა მიმართულებებს სვეტებზე ურევ.
ინდექსის ტიპები და როდის არის თითოეული სწორი#
| ტიპი | რაში არის კარგი | ტიპური გამოყენება |
|---|---|---|
| B-tree | ტოლობა, დიაპაზონები, დახარისხება | თითქმის ყველაფერი. ნაგულისხმევი |
| GIN | ბევრი მნიშვნელობა ერთ სვეტში | jsonb, მასივები, სრული ტექსტის ძებნა, trigram-ები |
| GiST | გადაფარვა და მანძილი | დიაპაზონები, გეომეტრია და PostGIS, უახლოესი მეზობელი |
| BRIN | უზარმაზარი ცხრილები, რომლებიც თანმიმდევრობითაა შენახული | მხოლოდ დამატებადი ლოგები და მოვლენები timestamp-ით |
| Hash | მხოლოდ ტოლობა | იშვიათად ღირს B-tree-ზე მეტად |
| SP-GiST | დაუბალანსებელი სტრუქტურები | Quadtree-ები, IP პრეფიქსები, ზოგი ტექსტური ძებნა |
ორი არანაგულისხმევი ტიპი, რომელიც ღირს ისწავლო, GIN და BRIN-ია.
GIN ინდექსირებს სვეტის შიგნით მყოფ მნიშვნელობებს და არა სვეტს მთლიანად. ასე ხდის jsonb-ზე containment query-ებს სწრაფს, ასე მუშაობს სრული ტექსტის ძებნა და - pg_trgm გაფართოებით - ასე ხდება წინა wildcard-იანი LIKE გამოსაყენებელი:
CREATE EXTENSION IF NOT EXISTS pg_trgm;CREATE INDEX customers_name_trgm ON app.customers USING gin (name gin_trgm_ops);-- now this can use an indexSELECT * FROM app.customers WHERE name ILIKE '%anders%';-- jsonb containmentCREATE INDEX events_payload ON app.events USING gin (payload jsonb_path_ops);SELECT * FROM app.events WHERE payload @> '{"type":"signup"}';jsonb_path_ops ნაგულისხმევ jsonb_ops-ზე უფრო პატარა ინდექსს აგებს და მხოლოდ containment ოპერატორს @> უჭერს მხარს, რაც ჩვეულებრივ სწორედ ის ოპერატორია, რომელიც გინდოდა. GIN ინდექსები B-tree-ებზე ნელა ახლდება და რამდენჯერმე დიდი შეიძლება იყოს, ამიტომ დადე ისინი იქ, სადაც სარგებელი მოაქვთ.
BRIN არაფერს ინახავს, გარდა ბლოკების დიაპაზონის შეჯამებისა - ცხრილის თითოეულ ნაწილში მინიმალურ და მაქსიმალურ მნიშვნელობას. ეს მას პაწაწინას ხდის (კილობაიტები იქ, სადაც B-tree გიგაბაიტებს დაიკავებდა) და სასარგებლოს მხოლოდ მაშინ, როცა ცხრილის ფიზიკური რიგი ინდექსირებულ სვეტს ემთხვევა, რაც პრაქტიკაში ნიშნავს მხოლოდ დამატებად ცხრილს, რომელიც მისი timestamp-ით არის ინდექსირებული. ცხრილზე, სადაც სტრიქონები შემთხვევითი რიგით მოდის, ის არარაობაზე უარესია.
Partial და expression ინდექსები#
ორი შესაძლებლობა, რომელიც დიდ ინდექსს პატარად აქცევს.
partial ინდექსი მხოლოდ იმ სტრიქონებს ფარავს, რომლებიც WHERE პირობას აკმაყოფილებს. კლასიკური შემთხვევა status სვეტია, სადაც ყოველთვის მხოლოდ ერთ მნიშვნელობას ეძებ:
CREATE INDEX orders_open ON app.orders (tenant_id, created_at) WHERE status = 'open';თუ შეკვეთების 2% ღიაა, ეს ინდექსი 2% ზომისაა, cache-ში ეტევა და ნაკლებ ძალისხმევას ითხოვს განახლებისას. planner მას მხოლოდ მაშინ იყენებს, როცა შეუძლია დაამტკიცოს, რომ query-ს პირობები ინდექსის WHERE პირობას გულისხმობს, ამიტომ პრედიკატი მისთვის ცნობადი ფორმით უნდა იყოს დაწერილი - გაამარტივე და ბუკვალურად დატოვე.
expression ინდექსი ფუნქციის შედეგს ინდექსირებს, ასე ხდება რეგისტრის მიმართ მგრძნობიარე ძებნა სწრაფი:
CREATE UNIQUE INDEX customers_email_lower ON app.customers (lower(email));SELECT * FROM app.customers WHERE lower(email) = lower($1);query-მ ზუსტად იგივე გამოსახულება უნდა გამოიყენოს, რაც ინდექსმა. lower(email) ინდექსში და email ILIKE $1 query-ში ერთმანეთს არ ემთხვევა. ფუნქცია ასევე immutable-ად უნდა იყოს მონიშნული, რაც გამორიცხავს ყველაფერს, რაც მიმდინარე დროს ან სესიის დროის სარტყელს ეხება.
ორივეს უნიკალურობასთან შეთავსება შეიძლება, რაც გაძლევს შეზღუდვებს, რომელთა გამოხატვა ტიპების სისტემას არ შეუძლია: ერთი აქტიური გამოწერა მომხმარებელზე, ერთი ძირითადი მისამართი ანგარიშზე.
CREATE UNIQUE INDEX one_active_sub ON app.subscriptions (customer_id) WHERE status = 'active';ინდექსები, რომლებიც უფასოდ გაქვს, და ის, რომელიც - არა#
primary key unique B-tree ინდექსს ქმნის. ასევე ნებისმიერი UNIQUE შეზღუდვა. სწორედ ამიტომ არის სტრიქონის id-ით მოძებნა სწრაფი შენი ჩარევის გარეშე.
foreign key-ები - არა. orders.customer_id REFERENCES customers(id) ქმნის ინდექსს customers.id-ზე (ის primary key-ა) და არაფერს orders.customer_id-ზე. მზარდ ცხრილზე ამის შემდეგ ორი რამ არასწორდ ხდება: SELECT ... WHERE customer_id = $1 მთელ ცხრილს სკანირებს, და მომხმარებლის წაშლა orders-ს შეზღუდვის შესამოწმებლად სკანირებს, ამ დროს ბლოკს აჩერებს. დააინდექსირე შენი foreign key სვეტები, თუ არ იცი, რომ შვილობილი ცხრილი პატარა დარჩება.
ორი დეტალი უნიკალურობაზე. null-ები ერთმანეთისგან ნაგულისხმევად განსხვავებულია, ამიტომ unique სვეტს ბევრი null შეიძლება ჰქონდეს; PostgreSQL 15-მა დაამატა UNIQUE NULLS NOT DISTINCT, თუ სხვა ქცევა გინდა. და foreign key-ს სჭირდება უბრალო unique ინდექსი მითითებულ სვეტებზე - partial unique ინდექსი არ ვარგა, რაც უკვირს მათ, ვინც ის ზემოთ მოცემული ხრიკისთვის ააგო.
და ბოლოს, INCLUDE (PostgreSQL 11 და უფრო ახალი) B-tree-ს ტვირთის სვეტებს უმატებს იმისგან, რომ ისინი დახარისხების გასაღების ნაწილი გახდნენ. ის index-only scan-ების გასაკეთებლად არსებობს:
CREATE INDEX orders_lookup ON app.orders (tenant_id, created_at) INCLUDE (total, status);გეგმის წაკითხვა: ნამდვილად იყენებს თუ არა ინდექსს?#
EXPLAIN (ANALYZE, BUFFERS) ერთადერთი გზაა, რომ გაიგო. კვანძების სახელები გეუბნება, რა მოხდა:
- Seq Scan - ცხრილის ყველა გვერდი წაიკითხა. სწორია პატარა ცხრილებისთვის და query-ებისთვის, რომლებსაც სტრიქონების უმეტესობა სჭირდება.
- Index Scan - ინდექსი გაიარა და ყოველი შესაბამისი სტრიქონი ცხრილიდან ამოიღო.
- Bitmap Index Scan, რომელსაც მოყვება Bitmap Heap Scan - ბევრი დამთხვევა, ამიტომ PostgreSQL მათ აგროვებს და ცხრილს ფიზიკური თანმიმდევრობით კითხულობს. ხშირად სწორი გეგმაა და მინიშნება, რომ შეიძლება უფრო შერჩევითი ინდექსი არსებობდეს.
- Index Only Scan - ყველაფერი, რაც query-ს სჭირდებოდა, ინდექსში იყო. სამიდან ყველაზე სწრაფი და ის, რომელიც vacuum-ზეა დამოკიდებული.
ეს ბოლო დამოკიდებულება ნაკლებად აშკარაა. ინდექსი არ ინახავს, ხილულია თუ არა სტრიქონის ვერსია შენი ტრანზაქციისთვის, ამიტომ index-only scan მაინც ამოწმებს ცხრილს, თუ visibility map არ ამბობს, რომ მთელი გვერდი ყველასთვის ხილულია. ამ ბიტებს vacuum ადგენს. ცხრილი, რომელსაც autovacuum ვერ ეწევა, ჩუმად კარგავს თავის index-only scan-ებს, რაც ერთ-ერთი გზაა, რომლითაც vacuum და bloat query-ის სიჩქარის პრობლემად იქცევა. თუ გეგმა ამბობს Heap Fetches: 84213, სწორედ ამას უყურებ.
seq scan ავტომატურად შეცდომა არ არის. 500 სტრიქონიან ცხრილზე ის ნებისმიერ ინდექსზე სწრაფია, და query-ზე, რომელიც ცხრილის 40%-ს აბრუნებს, ინდექსი მეტ შემთხვევით წაკითხვას ნიშნავს, ვიდრე ერთი სწორი გავლა. planner წყვეტს cost-ის მუდმივებითა და სტატისტიკით, ამიტომ თუ ცუდად არჩევს, შეამოწმე, რომ ANALYZE ბოლო დროს გაეშვა და რომ random_page_cost შენს საცავს ასახავს - ორივე PostgreSQL-ის მორგება პატარა სერვერებისთვის-შია. თავად გეგმის კითხვა ცალკე უნარია: EXPLAIN ANALYZE ნელ query-ზე სტრიქონ-სტრიქონ გადის.
როცა ინდექსი ზიანს აყენებს#
ფასი რეალურია და პატარა სერვერზე ჩანს.
ჩაწერა. ყოველი INSERT და DELETE ცხრილის ყველა ინდექსს ანახლებს. UPDATE - ასევე, თუ PostgreSQL heap-only tuple განახლებას ვერ გამოიყენებს - რაც შეუძლია მხოლოდ მაშინ, როცა არცერთი ინდექსირებული სვეტი არ შეცვლილა და გვერდზე თავისუფალი ადგილია. ინდექსის დამატება სვეტზე, რომელსაც შენი აპლიკაცია მუდმივად ანახლებს, ამ ოპტიმიზაციას აცილებს და ჩაწერის ფასს ამრავლებს.
დისკი და cache. ინდექსები შენი გეგმის საცავს ხარჯავს, და რაც უფრო მნიშვნელოვანია, მეხსიერებას: ინდექსი, რომელსაც არავინ იყენებს, მაინც იმავე cache-ისთვის ეჯიბრება, როგორც შენთვის საჭირო ინდექსი.
დაგეგმვა. ყოველი დამატებითი ინდექსი კიდევ ერთი ვარიანტია, რომელსაც planner აფასებს, და გადაფარული ინდექსების ნაკრები ცუდი არჩევანის ალბათობას ზრდის და არა ამცირებს.
Bloat. ინდექსები ისევე იბერება, როგორც ცხრილები, და გაბერილი ინდექსი უფრო ნელი ინდექსია.
კონკრეტული შემთხვევები, როცა ინდექსი არასწორი პასუხია: სვეტები ძალიან ცოტა განსხვავებული მნიშვნელობით (ლოგიკური flag, სადაც სტრიქონების ნახევარი true-ა - გამოიყენე partial ინდექსი იშვიათ მნიშვნელობაზე), რამდენიმე ათასზე ნაკლები სტრიქონის ცხრილები, სვეტები, რომლებიც მხოლოდ იკითხება SELECT-ში და ფილტრად არასოდეს გამოიყენება, და მესამე ინდექსი, რომლის სვეტები უკვე არსებული ინდექსის წამყვანი სვეტებია.
ინდექსების შექმნა და წაშლა ცხრილის ჩაკეტვის გარეშე#
უბრალო CREATE INDEX ბლოკს იღებს, რომელიც ცხრილში ჩაწერას მთელი აგების განმავლობაში აჩერებს. დიდ ცხრილზე სამუშაო საათებში ეს გათიშვაა.
CREATE INDEX CONCURRENTLY orders_tenant_created ON app.orders (tenant_id, created_at);CONCURRENTLY ცხრილს ორჯერ გადის და წაკითხვასა და ჩაწერას მთელი დროის განმავლობაში აგრძელებინებს. კომპრომისები ღირს იცოდე, სანამ მასზე დაეყრდნობი: ის უფრო დიდხანს გრძელდება, ვერ გაეშვება ტრანზაქციის ბლოკში (ამიტომ მიგრაციის ხელსაწყოებს ხშირად flag სჭირდება, რომ ის ბლოკის გარეთ გაუშვან), და თუ ჩავარდა, ტოვებს არავალიდურ ინდექსს, რომელიც ყოველ ჩაწერაზე ინახება, მაგრამ query-ებისთვის არასოდეს გამოიყენება. იპოვე ისინი და წაშალე:
SELECT indexrelid::regclass AS index, indrelid::regclass AS tableFROM pg_index WHERE NOT indisvalid;DROP INDEX CONCURRENTLY orders_tenant_created;ხელახალი აგება იმავე ნიმუშს მიჰყვება REINDEX INDEX CONCURRENTLY-ით (PostgreSQL 12 და უფრო ახალი). სესიისთვის maintenance_work_mem-ის გაზრდა ნებისმიერ მათგანს უფრო სწრაფად ასრულებს. ინდექსის დამატება სქემის ცვლილებაა, ისევე როგორც ნებისმიერი სხვა, ამიტომ სქემის მიგრაციები გათიშვის გარეშე-ის თანმიმდევრობის რჩევა მოქმედებს: დაამატე ის თავის ნაბიჯში, იმ კოდამდე, რომელსაც ის სჭირდება.
გამოუყენებელისა და გამოტოვებულის პოვნა#
PostgreSQL მრიცხველებს ინახავს. გამოიყენე ისინი აზრების ნაცვლად.
-- Indexes nobody has used, largest firstSELECT s.relname AS table, s.indexrelname AS index, s.idx_scan AS scans, pg_size_pretty(pg_relation_size(s.indexrelid)) AS sizeFROM pg_stat_user_indexes sJOIN pg_index i ON i.indexrelid = s.indexrelidWHERE s.idx_scan = 0 AND NOT i.indisuniqueORDER BY pg_relation_size(s.indexrelid) DESC;-- Tables being read sequentially the mostSELECT relname, seq_scan, seq_tup_read, idx_scan, seq_tup_read / GREATEST(seq_scan, 1) AS rows_per_scanFROM pg_stat_user_tablesWHERE seq_scan > 0ORDER BY seq_tup_read DESCLIMIT 10;ორი გაფრთხილება პირველ query-ზე. მრიცხველები ნულდება, როცა სტატისტიკა ნულდება ან სერვერი თავიდან აიგება, ამიტომ ნული სერვერზე, რომელიც გუშინ გადაიტვირთა, არაფერს ნიშნავს; PostgreSQL 16 და უფრო ახალი ამატებს last_idx_scan-ს, რომელსაც უფრო ადვილად ენდობი. და არასოდეს წაშალო unique ინდექსი იმ მიზეზით, რომ ის გამოუყენებელია - ის შეზღუდვას ასრულებს, ვინმე მას query-ს უკეთებს თუ არა.
მეორე query პასუხებს კი არა, კანდიდატებს პოულობს. ცხრილი, რომლის მილიონობით სტრიქონი თანმიმდევრულად იკითხება, გეუბნება, რომ query მას განმეორებით სკანირებს; pg_stat_statements გეუბნება, რომელი query, და EXPLAIN - რომელი ინდექსი გამოასწორებდა. როცა planner-ის შეფასებები ორ კორელირებულ სვეტზე ძალიან შორსაა, გაფართოებულ სტატისტიკას (CREATE STATISTICS) შეუძლია შეფასება ახალი ინდექსის გარეშე გაასწოროს.
FAQ#
რამდენი ინდექსია ერთ ცხრილზე ძალიან ბევრი?
ფიქსირებული რიცხვი არ არსებობს, მაგრამ ხუთ-ექვსზე მეტი ცხრილზე, რომელიც მუდმივ ჩაწერას იღებს, უნდა შეგეძლოს დაასახელო query, რომელსაც თითოეული ემსახურება. თუ არ შეგიძლია, შეამოწმე idx_scan და წაშალე ისინი, რომლებსაც არავინ იყენებს.
რატომ არ გამოიყენება ჩემი ინდექსი?
სამი ჩვეულებრივი მიზეზი: query სვეტს ფუნქციაში ან cast-ში ახვევს, query ფილტრავს სვეტზე, რომელიც composite ინდექსის წამყვანი სვეტი არ არის, ან ცხრილი იმდენად პატარაა, რომ თანმიმდევრული სკანირება მართლა უფრო იაფია. EXPLAIN რამდენიმე წამში წყვეტს.
უნდა დავაინდექსირო ყველა foreign key სვეტი?
დააინდექსირე ისინი, რომლებზეც ფილტრავ ან join-ს აკეთებ, და ისინი, რომელთა მშობელი სტრიქონები იშლება ან ახლდება. პატარა შვილობილ ცხრილზე ეს არ აქვს მნიშვნელობა; დიდზე ინდექსის არქონა მშობლის ყოველ წაშლას ბლოკის ქვეშ სრულ სკანირებად აქცევს.
სჭირდება ინდექსებს მოვლა?
ისინი ავტომატურად ახლდება, მაგრამ იბერება, როცა სტრიქონები ახლდება და იშლება. REINDEX INDEX CONCURRENTLY ერთს ჩაწერის ბლოკის გარეშე აღადგენს. მონაცემთა ბაზების უმეტესობას ეს არასოდეს სჭირდება; ცხრილს ინტენსიური ცვლილებით - სჭირდება.
რა არის index-only scan და რატომ შეწყდა ჩემი?
ეს გეგმაა, სადაც ყველა სვეტი, რომელიც query-ს სჭირდება, ინდექსშია, ამიტომ ცხრილს არასოდეს ეხება. ის მოითხოვს, რომ visibility map გვერდებს ყველასთვის ხილულად ნიშნავდეს, რასაც მხოლოდ vacuum აკეთებს. თუ autovacuum ჩამორჩა, გეგმა ჩუმად გადადის heap-იდან სტრიქონების ამოღებაზე.
composite ინდექსი სჯობს თუ რამდენიმე ერთსვეტიანი?
composite ინდექსი, როცა სვეტები ერთად და სწორი თანმიმდევრობით გამოიყენება ერთ query-ში. ერთსვეტიანი ინდექსები უკეთესია, როცა სვეტებს სხვადასხვა query დამოუკიდებლად იყენებს. PostgreSQL-ს ორი ინდექსის გაერთიანება bitmap scan-ში შეუძლია, მაგრამ ეს სათადარიგო ვარიანტია და არა გეგმა, რომელსაც უნდა მიელტვოდე.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.