it-swarm-eu.dev

Zpracování indexů v PostgreSQL

Mám pár otázek ohledně fungování indexů v PostgreSQL. Mám tabulku Friends s následujícím indexem:

   Friends ( user_id1 ,user_id2) 

user_id1 a user_id2 jsou cizí klíče do tabulky user

  1. Jsou tyto ekvivalenty? Pokud ne, tak proč?

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
    
  2. Pokud vytvořím primární klíč (user_id1, user_id2), vytvoří pro něj automaticky indexy a

    Pokud indexy v první otázce nejsou rovnocenné, pak který index se vytvoří nad příkazem primárního klíče?

80
codecool

Zde jsou výsledky dotazování tabulky na druhý sloupec indexu více sloupců.
Účinky jsou pro každého snadno reprodukovatelné. Jen to zkuste doma.

Testoval jsem s PostgreSQL 9.0.5 na Debianu pomocí středně velké tabulky databáze skutečných životů s 23322 řádky. Implementuje vztah n: m mezi tabulkami adr (adresa) a att (atribut), ale to zde není relevantní. Zjednodušené schéma:

CREATE TABLE adratt (
  adratt_id serial PRIMARY KEY
, adr_id    integer NOT NULL
, att_id    integer NOT NULL
, log_up    timestamp(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);

Omezení UNIQUE efektivně implementuje jedinečný index. Zopakoval jsem test s jasným indexem, abych si byl jistý, a dostal stejné výsledky podle očekávání.

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

Tabulka je seskupena na adratt_uni index a před testem jsem běžel:

CLUSTER adratt;
ANALYZE adratt;

Sekvenční vyhledávání dotazů na (adr_id, att_id) jsou tak rychlé, jak jen mohou být. Vícesloupcový index bude stále použit pro podmínku dotazu ve druhém sloupci indexu.

Několikrát jsem dotazy spustil, abych naplnil mezipaměť a vybral jsem nejlepší z deseti běhů, abychom získali srovnatelné výsledky.

1. Dotaz pomocí obou sloupců

SELECT *
FROM   adratt
WHERE  att_id = 90
AND    adr_id = 10;

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
(1 row)

Výstup EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
  Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms

2. Dotaz pomocí prvního sloupce

SELECT * FROM adratt WHERE adr_id = 10

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       126 |     10 |     10 | 2008-07-29 09:35:54
       125 |     10 |     13 | 2008-07-29 09:35:54
      4711 |     10 |     21 | 2008-07-29 09:35:54
     29322 |     10 |     22 | 2011-06-06 15:50:38
     29321 |     10 |     30 | 2011-06-06 15:47:17
       124 |     10 |     62 | 2008-07-29 09:35:54
     21913 |     10 |     78 | 2008-07-29 09:35:54
       123 |     10 |     90 | 2008-07-29 09:35:54
     28352 |     10 |    106 | 2010-11-22 12:37:50
(9 rows)

Výstup EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
  Index Cond: (adr_id = 10)
Total runtime: 0.058 ms

3. Dotaz pomocí druhého sloupce

SELECT * FROM adratt WHERE att_id = 90

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
       180 |     39 |     90 | 2008-08-29 15:46:07
...
(83 rows)

Výstup EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1)
  Index Cond: (att_id = 90)
Total runtime: 0.849 ms

4. Zakažte indexscan & bitmapscan

SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90

Výstup EXPLAIN ANALYZE:

Bitmap Heap Scan on adratt  (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1)
  Recheck Cond: (att_id = 90)
  ->  Bitmap Index Scan on adratt_uni  (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1)
        Index Cond: (att_id = 90)
Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90

Výstup EXPLAIN ANALYZE:

Seq Scan on adratt  (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1)
  Filter: (att_id = 90)
Total runtime: 2.680 ms

Závěr

Jak se očekávalo, index více sloupců se použije pro dotaz pouze ve druhém sloupci.
Jak se očekávalo, je méně efektivní, ale dotaz je stále x rychlejší než bez indexu.
Po zakázání skenování indexů vybere plánovač dotazů sken haldy bitmap, který funguje téměř stejně rychle. Teprve po deaktivaci se také vrátí zpět k sekvenčnímu skenování.

83

znovu 1) Ano a ne.

Pro dotaz, který používá oba sloupce, např. where (user_id1, user_id2) = (1,2) nezáleží na tom, který index je vytvořen.

Pro dotaz, který má podmínku pouze v jednom ze sloupců, např. where user_id1 = 1 Na tom záleží, protože obvykle lze pro porovnání pomocí optimalizátoru použít pouze „vedoucí“ sloupce. Takže where user_id1 = 1 By byl schopen použít index (user_id1, user_id2), ale nebyl by schopen index (user_id2, user_id1) pro všechny případy.

Poté, co jsme si s tím pohrávali (poté, co nám Erwin tak laskavě ukázal nastavení, kde to funguje), se zdá, že to velmi závisí na distribuci dat ve druhém sloupci, ačkoli jsem dosud nezjistil, která situace umožňuje optimalizátoru použít koncové sloupce. pro podmínku KDE.

Oracle 11, který může také (někdy) používat sloupce, které nejsou na začátku definice indexu.

znovu 2) Ano, vytvoří index

Citace z manuál

Přidání primárního klíče automaticky vytvoří jedinečný index bez sloupců ve sloupci nebo skupině sloupců použitých v primárním klíči.

re 2a) Primary Key (user_id1,user_id2) vytvoří index na (user_id1, user_id2) (o kterém se můžete sami přesvědčit velmi snadno jednoduše vytvoření takového primárního klíče)

Velmi doporučuji, abyste si přečetli kapitola o indexech v manuál , v podstatě odpovídá na všechny výše uvedené otázky.

Navíc Jaký index vytvořit? od Depesz odvádí dobrou práci vysvětlující pořadí ve sloupcích indexu a dalších tématech souvisejících s indexem.

30

Ad 1)
V PostgreSQL existují omezení například @a_horse_with_no_name popisuje . Až do verze 8. více sloupcové indexy mohly být použity pouze pro dotazy na předních sloupcích. Ve verzi 8.1 to bylo vylepšeno. aktuální příručka pro Postgres 1 (aktualizováno) vysvětluje:

Index s více sloupci B-stromu lze použít s podmínkami dotazu, které zahrnují jakoukoli podmnožinu sloupců indexu, ale index je nejúčinnější, pokud existují omezení na předních sloupcích (nejvíce vlevo). Přesné pravidlo je, že omezení rovnosti v předních sloupcích plus jakákoli omezení nerovnosti v prvním sloupci, která nemá omezení rovnosti, budou použita k omezení části skenovaného indexu. Omezení sloupců napravo od těchto sloupců jsou kontrolovány v indexu, takže ukládají návštěvy samotné tabulky, ale nesnižují část indexu, který má být skenován. Například vzhledem k indexu (a, b, c) a podmínka dotazu WHERE a = 5 AND b >= 42 AND c < 77, index by musel být skenován od první položky s a = 5 a b = 42 nahoru přes poslední položku s a = 5. Položky indexu s c> = 77 by bylo přeskočeno, ale stále by museli být prohledáni. Tento index lze v zásadě použít pro dotazy, které mají omezení na b a/nebo c bez omezení na a - ale celý index by musel být skenován, takže ve většině případů by plánovač upřednostnil sekvenční skenování tabulky před použitím indexu.

Důraz na můj. To mohu potvrdit ze zkušenosti.
Viz také přidaný testovací případ moje pozdější odpověď zde .

12

Toto je odpověď na Jackova odpověď , komentář by neudělal.

Před verzí 9.2 neexistovaly žádné krycí indexy v PostgreSQL . Vzhledem k modelu MVCC musí být pro kontrolu viditelnosti navštívena každá Tuple v sadě výsledků. Možná uvažujete o společnosti Oracle.

Vývojáři PostgreSQL hovoří o „indexových skenech“ . Ve skutečnosti byla tato funkce vydána s Postgresem 9.2. Přečtěte si potvrzovací zpráv .
Depesz napsal velmi informativní blogový příspěvek .

Pravda pokrývající indexy (aktualizace) jsou zavedeny s klauzulí INCLUDE s Postgres 11. Související:

Tohle je také trochu mimo:

spoléhá se na skutečnost, že „úplné prověřování“ indexu je často rychlejší než „úplné prověřování“ indexované tabulky kvůli zvláštním sloupcům v tabulce, které se v indexu neobjevují.

Jak bylo uvedeno v komentářích k mé druhé odpovědi, také jsem provedl testy s tabulkou dvou celých čísel a nic jiného. Index obsahuje stejné sloupce jako tabulka. Velikost indexu btree je přibližně 2/3 velikosti tabulky. Nestačí vysvětlit zrychlení faktoru 3. Spustil jsem další test na základě vašeho nastavení, zjednodušený na dva sloupce a se 100 000 řádky. V mé instalaci PostgreSQL 9.0 byly výsledky konzistentní.

Pokud tabulka obsahuje další sloupce, zrychlení s indexem se stává podstatnějším, ale to rozhodně není jediným faktorem .

Souhrn hlavních bodů:

  • Vícenásobné sloupce mohou být použity s dotazy na nevodící sloupce, ale zrychlení je pouze kolem faktoru 3 pro selektivní kritéria (malé procento řádků ve výsledku). Vyšší pro větší tuple, nižší pro větší části tabulky ve výsledkové sadě.

  • Pokud je výkon důležitý, vytvořte v těchto sloupcích další index.

  • Pokud jsou všechny zúčastněné sloupce zahrnuty do indexu (krycí index) a všechny zúčastněné řádky (na blok) jsou viditelné pro všechny transakce, můžete získat "kontrola pouze indexu" v pg 9.2 nebo novější.

12
  1. Jsou tyto ekvivalenty? Pokud ne, tak proč?

    Index (user_id1, user_id2) a Index (user_id2, user_id1)

Nejsou ekvivalentní a obecně řečeno index (bar, baz) nebude pro dotazy ve tvaru select * from foo where baz=?

Erwin prokázal , že takové indexy mohou skutečně zrychlit dotaz, ale tento efekt je omezený a ne ve stejném pořadí, jak obecně očekáváte, že index zlepší vyhledávání - spoléhá se na skutečnost, že „plný“ skenování indexu je často rychlejší než „úplné skenování“ indexované tabulky kvůli zvláštním sloupcům v tabulce, které se v indexu neobjevují.

Shrnutí: indexy mohou pomoci dotazům i na sloupcích, které nejsou vedoucími, ale jedním ze dvou sekundárních a relativně malých způsobů a ne dramatickým způsobem obvykle očekáváte, že index díky své stručné struktuře pomůže

nb dva způsoby, jak může index pomoci, jsou, pokud je úplné prohledávání indexu výrazně levnější než úplné prohledávání tabulky a buď: 1. vyhledávání tabulky je levné (protože jich je jen málo nebo jsou seskupeny), nebo 2. index je pokrývající , takže neexistují vůbec žádné tabulky Jejda, viz komentáře Erwins zde

testbed:

create table foo(bar integer not null, baz integer not null, qux text not null);

insert into foo(bar, baz, qux)
select random()*100, random()*100, 'some random text '||g from generate_series(1,10000) g;

dotaz 1 (bez indexu, bít 74 vyrovnávacích pamětí ):

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.41..181.42 rows=1 width=32) (actual time=3.301..3.302 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..181.30 rows=43 width=32) (actual time=0.043..3.228 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.335 ms

dotaz 2 (s indexem - optimalizátor ignoruje index - znovu zasáhne 74 vyrovnávacích pamětí ):

create index bar_baz on foo(bar, baz);

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=199.12..199.13 rows=1 width=32) (actual time=3.277..3.277 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..199.00 rows=50 width=32) (actual time=0.043..3.210 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.311 ms

dotaz 2 (s indexem - a my trik optimalizátor používáme):

explain (buffers, analyze, verbose) select max(qux) from foo where bar>-1000 and baz=0;
                                                       QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=115.56..115.57 rows=1 width=32) (actual time=1.495..1.495 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=36 read=30
   ->  Bitmap Heap Scan on stack.foo  (cost=73.59..115.52 rows=17 width=32) (actual time=1.370..1.428 rows=52 loops=1)
         Output: bar, baz, qux
         Recheck Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
         Buffers: shared hit=36 read=30
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..73.58 rows=17 width=0) (actual time=1.356..1.356 rows=52 loops=1)
               Index Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
               Buffers: shared read=30
 Total runtime: 1.535 ms

Přístup přes index je tedy v tomto případě dvakrát rychlejší 30 vyrovnávacích pamětí - což je z hlediska indexování „mírně rychlejší“! A YMMV v závislosti na relativní velikost tabulky a indexu spolu s počtem filtrovaných řádků a charakteristik shlukování dat v tabulce

Naproti tomu dotazy na předním sloupci využívají btree strukturu indexu - v tomto případě zasažení 2 vyrovnávací paměti :

explain (buffers, analyze, verbose) select max(qux) from foo where bar=0;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=75.70..75.71 rows=1 width=32) (actual time=0.172..0.173 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=38
   ->  Bitmap Heap Scan on stack.foo  (cost=4.64..75.57 rows=50 width=32) (actual time=0.036..0.097 rows=59 loops=1)
         Output: bar, baz, qux
         Recheck Cond: (foo.bar = 0)
         Buffers: shared hit=38
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..4.63 rows=50 width=0) (actual time=0.024..0.024 rows=59 loops=1)
               Index Cond: (foo.bar = 0)
               Buffers: shared hit=2
 Total runtime: 0.209 ms