Postgres dizin taraması yerine sıralı tarama gerçekleştiriyor


9

İçinde yaklaşık 10 milyon satır içeren bir tablo ve tarih alanında bir dizin var. Endeksli alanın benzersiz değerlerini ayıklamaya çalıştığımda, sonuç kümesinde yalnızca 26 öğe olmasına rağmen Postgres sıralı bir tarama çalıştırıyor. Doktor bu planı neden seçiyor? Bundan kaçınmak için ne yapabilirim?

Diğer cevaplar bu dizin kadar sorgu ile ilgili olduğundan şüpheleniyorum.

explain select "labelDate" from pages group by "labelDate";
                              QUERY PLAN
-----------------------------------------------------------------------
 HashAggregate  (cost=524616.78..524617.04 rows=26 width=4)
   Group Key: "labelDate"
   ->  Seq Scan on pages  (cost=0.00..499082.42 rows=10213742 width=4)
(3 rows)

Tablo yapısı:

http=# \d pages
                                       Table "public.pages"
     Column      |          Type          |        Modifiers
-----------------+------------------------+----------------------------------
 pageid          | integer                | not null default nextval('...
 createDate      | integer                | not null
 archive         | character varying(16)  | not null
 label           | character varying(32)  | not null
 wptid           | character varying(64)  | not null
 wptrun          | integer                | not null
 url             | text                   |
 urlShort        | character varying(255) |
 startedDateTime | integer                |
 renderStart     | integer                |
 onContentLoaded | integer                |
 onLoad          | integer                |
 PageSpeed       | integer                |
 rank            | integer                |
 reqTotal        | integer                | not null
 reqHTML         | integer                | not null
 reqJS           | integer                | not null
 reqCSS          | integer                | not null
 reqImg          | integer                | not null
 reqFlash        | integer                | not null
 reqJSON         | integer                | not null
 reqOther        | integer                | not null
 bytesTotal      | integer                | not null
 bytesHTML       | integer                | not null
 bytesJS         | integer                | not null
 bytesCSS        | integer                | not null
 bytesHTML       | integer                | not null
 bytesJS         | integer                | not null
 bytesCSS        | integer                | not null
 bytesImg        | integer                | not null
 bytesFlash      | integer                | not null
 bytesJSON       | integer                | not null
 bytesOther      | integer                | not null
 numDomains      | integer                | not null
 labelDate       | date                   |
 TTFB            | integer                |
 reqGIF          | smallint               | not null
 reqJPG          | smallint               | not null
 reqPNG          | smallint               | not null
 reqFont         | smallint               | not null
 bytesGIF        | integer                | not null
 bytesJPG        | integer                | not null
 bytesPNG        | integer                | not null
 bytesFont       | integer                | not null
 maxageMore      | smallint               | not null
 maxage365       | smallint               | not null
 maxage30        | smallint               | not null
 maxage1         | smallint               | not null
 maxage0         | smallint               | not null
 maxageNull      | smallint               | not null
 numDomElements  | integer                | not null
 numCompressed   | smallint               | not null
 numHTTPS        | smallint               | not null
 numGlibs        | smallint               | not null
 numErrors       | smallint               | not null
 numRedirects    | smallint               | not null
 maxDomainReqs   | smallint               | not null
 bytesHTMLDoc    | integer                | not null
 maxage365       | smallint               | not null
 maxage30        | smallint               | not null
 maxage1         | smallint               | not null
 maxage0         | smallint               | not null
 maxageNull      | smallint               | not null
 numDomElements  | integer                | not null
 numCompressed   | smallint               | not null
 numHTTPS        | smallint               | not null
 numGlibs        | smallint               | not null
 numErrors       | smallint               | not null
 numRedirects    | smallint               | not null
 maxDomainReqs   | smallint               | not null
 bytesHTMLDoc    | integer                | not null
 fullyLoaded     | integer                |
 cdn             | character varying(64)  |
 SpeedIndex      | integer                |
 visualComplete  | integer                |
 gzipTotal       | integer                | not null
 gzipSavings     | integer                | not null
 siteid          | numeric                |
Indexes:
    "pages_pkey" PRIMARY KEY, btree (pageid)
    "pages_date_url" UNIQUE CONSTRAINT, btree ("urlShort", "labelDate")
    "idx_pages_cdn" btree (cdn)
    "idx_pages_labeldate" btree ("labelDate") CLUSTER
    "idx_pages_urlshort" btree ("urlShort")
Triggers:
    pages_label_date BEFORE INSERT OR UPDATE ON pages
      FOR EACH ROW EXECUTE PROCEDURE fix_label_date()

Yanıtlar:


8

Postgres optimizasyonu ile ilgili bilinen bir sorundur. Farklı değerler - sizin durumunuzda olduğu gibi - ve 8.4+ sürümündeyseniz, özyinelemeli bir sorgu kullanarak çok hızlı bir geçici çözüm burada açıklanır: Gevşek Indexscan .

Sorgunuz yeniden yazılabilir ( LATERAL9.3+ sürümü gerekir):

WITH RECURSIVE pa AS 
( ( SELECT labelDate FROM pages ORDER BY labelDate LIMIT 1 ) 
  UNION ALL
    SELECT n.labelDate 
    FROM pa AS p
         , LATERAL 
              ( SELECT labelDate 
                FROM pages 
                WHERE labelDate > p.labelDate 
                ORDER BY labelDate 
                LIMIT 1
              ) AS n
) 
SELECT labelDate 
FROM pa ;

Erwin Brandstetter, bu yanıtta (ilgili ancak farklı bir konuda) kapsamlı bir açıklamaya ve sorgunun birkaç varyasyonuna sahiptir: Kullanıcı başına en son kaydı almak için GROUP BY sorgusunu optimize edin


6

En iyi sorgu büyük ölçüde veri dağıtımına bağlıdır .

Her tarih için çok sayıda satırınız var , bu oluşturuldu. Durumunuz sonuçta yalnızca 26 değere düştüğünden, aşağıdaki çözümler tüm endeks kullanılır kullanılmaz çok hızlı olacaktır.
(Daha belirgin değerler için dava daha ilginç hale gelecektir.)

Dahil etmek gerek yoktur pageid hiç (siz yorum gibi).

indeks

Tek ihtiyacınız olan basit bir btree indeksi "labelDate".
Sütunda birkaçdan fazla NULL değeri olduğunda, kısmi bir dizin biraz daha fazla yardımcı olur (ve daha küçüktür):

CREATE INDEX pages_labeldate_nonull_idx ON big ("labelDate")
WHERE  "labelDate" IS NOT NULL;

Daha sonra açıkladınız:

0% NULL ancak içe aktarırken işleri düzelttikten sonra.

Kısmi indeks , NULL değerleri olan satırların ara durumlarını dışlamak için hala anlamlı olabilir . Endeks gereksiz gereksiz güncellemeler önlemek (sonuçta şişkinlik ile).

Sorgu

Geçici bir aralığa dayanarak

Tarihleriniz çok fazla boşluk olmayan sürekli bir aralıkta görünüyorsa , veri türünün niteliğini dateavantajımız için kullanabiliriz. Verilen iki değer arasında yalnızca sınırlı, sayılabilir bir değer vardır. Boşluklar azsa, bu en hızlı olacaktır:

SELECT d."labelDate"
FROM  (
   SELECT generate_series(min("labelDate")::timestamp
                        , max("labelDate")::timestamp
                        , interval '1 day')::date AS "labelDate"
   FROM   pages
   ) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

Neden hiç dökme timestampiçinde generate_series()? Görmek:

Min ve maks indeksten ucuza alınabilir. Eğer varsa bilmek minimum ve / veya maksimum olası tarih, henüz, biraz daha ucuz olur. Misal:

SELECT d."labelDate"
FROM  (SELECT date '2011-01-01' + g AS "labelDate"
       FROM   generate_series(0, now()::date - date '2011-01-01' - 1) g) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

Veya değişmez bir aralık için:

SELECT d."labelDate"
FROM  (SELECT date '2011-01-01' + g AS "labelDate"
       FROM generate_series(0, 363) g) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

Gevşek dizin taraması

Bu, tarihlerin herhangi bir dağılımı ile çok iyi performans gösterir (tarih başına çok sayıda satırımız olduğu sürece). Temelde @ypercube zaten sağlanan . Ancak bazı iyi noktalar var ve favori dizinimizin her yerde kullanılabileceğinden emin olmalıyız.

WITH RECURSIVE p AS (
   ( -- parentheses required for LIMIT
   SELECT "labelDate"
   FROM   pages
   WHERE  "labelDate" IS NOT NULL
   ORDER  BY "labelDate"
   LIMIT  1
   ) 
   UNION ALL
   SELECT (SELECT "labelDate" 
           FROM   pages 
           WHERE  "labelDate" > p."labelDate" 
           ORDER  BY "labelDate" 
           LIMIT  1)
   FROM   p
   WHERE  "labelDate" IS NOT NULL
   ) 
SELECT "labelDate" 
FROM   p
WHERE  "labelDate" IS NOT NULL;
  • İlk CTE petkili bir şekilde aynıdır

    SELECT min("labelDate") FROM pages

    Ancak ayrıntılı form, kısmi dizinimizin kullanılmasını sağlar. Ayrıca, bu form genellikle deneyimlerimde (ve testlerimde) biraz daha hızlı.

  • Sadece tek bir sütun için, rCTE'nin özyinelemedeki ilişkili alt sorguları biraz daha hızlı olmalıdır. Bunun için "labelDate" için NULL ile sonuçlanan satırların hariç tutulması gerekir. Görmek:

  • Kullanıcı başına en son kaydı almak için GROUP BY sorgusunu optimize edin

asides

Tırnaksız, yasal, küçük harf tanımlayıcıları hayatınızı kolaylaştırır.
Disk alanından tasarruf etmek için tablo tanımınızdaki sütunları olumlu bir şekilde sipariş edin:


-2

Postgresql belgelerinden:

CLUSTER, belirtilen dizinde bir dizin taraması veya (dizin bir b ağacı ise) ardışık bir tarama ve ardından sıralama kullanarak tabloyu yeniden sıralayabilir . Planlayıcı maliyet parametrelerine ve mevcut istatistiksel bilgilere dayanarak daha hızlı olacak yöntemi seçmeye çalışacaktır.

LabelDate dizininiz bir ağaçtır ..

Referans:

http://www.postgresql.org/docs/9.1/static/sql-cluster.html


`` NEREDE '' etiketi gibi bir durumda bileTarih "2000-01-01" ve "2020-01-01" arasında bir ardışık tarama içerir.
Charlie Clark

Şu anda kümeleme (veriler kabaca bu sırayla girilmiş olsa da). Bu hala sorgu planlayıcısı WHERE yan tümcesinde bile bir dizin kullanmama kararını gerçekten açıklamıyor.
Charlie Clark

Ayrıca oturum için sıralı taramayı devre dışı bırakmayı denediniz mi? set enable_seqscan=offHer durumda dokümantasyon açıktır. Kümelenirseniz, sıralı bir tarama gerçekleştirir.
Fabrizio Mazzoni

Evet, sıralı taramayı devre dışı bırakmayı denedim ama çok fazla fark yaratmadı. Daha sonra gerçek sorgularda JOINS için kullanılabilecek bir arama tablosu oluşturmak için kullandığım için bu sorgunun hızı aslında çok önemli değil.
Charlie Clark
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.