Bir dizi zaman damgası aralığında sorguları optimize etme (iki sütun)


96

PostgreSQL 9.1'i Ubuntu 12.04'te kullanıyorum.

Bir süre içinde kayıtları seçmem gerekiyor: masamın time_limitsiki timestampalanı ve bir integerözelliği var. Gerçek tablomda bu sorguya dahil olmayan ilave sütunlar var.

create table (
   start_date_time timestamp,
   end_date_time timestamp, 
   id_phi integer, 
   primary key(start_date_time, end_date_time,id_phi);

Bu tablo yaklaşık 2M kayıt içermektedir.

Aşağıdaki gibi sorular çok fazla zaman aldı:

select * from time_limits as t 
where t.id_phi=0 
and t.start_date_time <= timestamp'2010-08-08 00:00:00'
and t.end_date_time   >= timestamp'2010-08-08 00:05:00';

Bu yüzden başka bir dizin eklemeye çalıştım - PK tersi:

create index idx_inversed on time_limits(id_phi, start_date_time, end_date_time);

Performansın arttığı izlenimini edindim: Masanın ortasındaki kayıtlara erişme süresi daha makul görünüyor: 40 ile 90 saniye arasında bir yerde.

Ancak, zaman aralığının ortasındaki değerler için hala birkaç on saniye. Masanın sonunu hedeflerken iki kez daha (kronolojik olarak konuşursak).

explain analyzeBu sorgu planını almak için ilk kez denedim :

 Bitmap Heap Scan on time_limits  (cost=4730.38..22465.32 rows=62682 width=36) (actual time=44.446..44.446 rows=0 loops=1)
   Recheck Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
   ->  Bitmap Index Scan on idx_time_limits_phi_start_end  (cost=0.00..4714.71 rows=62682 width=0) (actual time=44.437..44.437 rows=0 loops=1)
         Index Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
 Total runtime: 44.507 ms

Depesz.com'daki sonuçlara bakın.

Aramayı optimize etmek için ne yapabilirim? Bir zaman id_phiolarak ayarlanmış iki zaman damgası sütununu taramak için harcanan süreyi görebilirsiniz 0. Ve zaman damgalarındaki büyük taramayı (60K satır!) Anlamıyorum. Birincil anahtar tarafından dizine idx_inversedeklenmemişler mi, ekledim mi?

Zaman damgası türlerinden başka bir şeye geçmeli miyim?

GIST ve GIN endeksleri hakkında biraz okudum. Özel tipler için belirli şartlar üzerinde daha etkili olabileceklerini biliyorum. Kullanım davam için uygun bir seçenek mi?


1
iyi 45s. Neden 45ms yazdığını bilmiyorum. 45ms kadar hızlı olsaydı bile şikayet etmeye bile başlamamıştım ... :-) Belki de analizin çıktısındaki bir hata. Veya belki de analizin yapılma zamanıdır. Dunno. Fakat 40/50 saniye ölçtüğüm şey.
Stephane Rolland

2
explain analyzeÇıktıda bildirilen süre , sunucuda gereken sorgunun zamanıdır . Sorgu 45 saniye sürer, daha sonra ek sefer 62.682 satır var ve her satır büyükse (örneğin uzun sahiptir Sonuçta sorguyu çalıştıran programa veritabanından veri aktarımı harcanan varcharveya textsütunlar), bu olabilir aktarım süresini etkileyebilir şiddetle, etkili şekilde.
a_horse_with_no_name

@ a_horse_with_no_name: rows=62682 rowsplanlayıcının tahminidir . Sorgu 0 satır döndürür. (actual time=44.446..44.446 rows=0 loops=1)
Erwin Brandstetter

@ErwinBrandstetter: ah, doğru. Bunu görmezden geldim. Fakat yine de, açıklamanın analizinin yürütme süresi hakkındaki yalanını görmedim.
a_horse_with_no_name

Yanıtlar:


162

Postgres 9.1 veya üstü için:

CREATE INDEX idx_time_limits_ts_inverse
ON time_limits (id_phi, start_date_time, end_date_time DESC);

Çoğu durumda bir endeksin sıralama düzeni pek ilgili değildir. Postgres'ler pratik olarak hızlı bir şekilde geriye doğru tarama yapabilir. Ancak, birden çok sütundaki aralık sorguları için çok büyük bir fark yaratabilir. Yakından alakalı:

Sorgunuzu düşünün:

SELECT *
FROM   time_limits
WHERE  id_phi = 0
AND    start_date_time <= '2010-08-08 00:00'
AND    end_date_time   >= '2010-08-08 00:05';

Dizindeki ilk sütunun sıralama düzeni id_phiilgisizdir. Eşitlik için kontrol edildiğinden ( =) önce gelmelidir. Haklısın. Bu cevapta daha fazlası:

Postgres id_phi = 0zamanın hemen yanına atlayabilir ve eşleşen dizinin aşağıdaki iki sütununu göz önünde bulundurabilir. Bunlar, ters sıralama düzeninin ( <=, >=) aralık koşullarıyla sorgulanır . Dizimde, sıralama satırları önce gelir. B-Tree index 1 ile mümkün olan en hızlı yol olmalı :

  • İstediğiniz start_date_time <= something: dizin önce en erken zaman damgasına sahip.
    • Hak
      kazanıyorsa , aynı zamanda 3. sütunu da kontrol edin .
  • İstediğiniz end_date_time >= something: index ilk önce en son zaman damgasına sahip.
    • Uygunsa, ilkleri geçene kadar satırları almaya devam et (süper hızlı).
      Sütun 2 için bir sonraki değerle devam edin.

Postgresler ileri veya geri tarama yapabilir . Dizine sahip olduğunuz şekilde , ilk iki sütunda eşleşen tüm satırları okumalı ve ardından üçüncü filtreyi filtrelemelidir . Dizinler veORDER BY kılavuzdaki bölümleri mutlaka okuyun . Sorunuza oldukça iyi uyar.

İlk iki sütunda kaç satır eşleşir? Tablonun zaman aralığının başlangıcına yakın
sadece birkaçı start_date_time. Ancak masanın kronolojik sonunda hemen hemen tüm satırlar id_phi = 0! Böylece performans daha sonraki başlangıç ​​zamanlarında bozulur.

Planlayıcı tahminleri

Planlayıcı rows=62682, örnek sorgunuz için tahmin eder . Bunlardan hiçbiri kalifiye değil ( rows=0). Tablo için istatistik hedefini arttırırsanız daha iyi tahminler alabilirsiniz. 2.000.000 satır için ...

ALTER TABLE time_limits ALTER start_date_time SET STATISTICS 1000;
ALTER TABLE time_limits ALTER end_date_time   SET STATISTICS 1000;

... ödeyebilir. Veya daha da yükseğe. Bu cevapta daha fazlası:

Sanırım buna ihtiyacın yok id_phi(sadece birkaç farklı değer, eşit olarak dağıtılmış), zaman damgaları için (eşit miktarda dağıtılmış birçok farklı değer var).
Ayrıca, geliştirilmiş endeks ile çok da önemli olduğunu düşünmüyorum.

CLUSTER / pg_repack

Daha hızlı olmasını istiyorsanız, tablonuzdaki satırların fiziksel sırasını düzenleyebilirsiniz. Tablonuzu yeniden yazmak ve dizine göre sıraları sıralamak için, tablonuzu kısa bir süre için (örneğin, kapalı saatlerde) yalnızca kilitlemeyi göze alabilirseniz:

ALTER TABLE time_limits CLUSTER ON idx_time_limits_inversed;

Eşzamanlı erişimle, aynısını özel kilitlemesiz yapabilen pg_repack'i düşünün .

Her iki durumda da, etki tablodan daha az blok okunması gerektiği ve her şeyin önceden sıralandığıdır. Fiziksel sıralama düzenini parçalayan masaya yazılan zamanla kötüleşen tek seferlik bir etkidir.

Postgres'te GiST Endeksi 9.2+

1 pg 9.2+ ile daha hızlı ve daha başka bir seçenek daha var: aralık sütunu için bir GiST dizini.

  • Orada yerleşik olan için aralık türlerinde timestampve timestamp with time zone: tsrange,tstzrange . Bir btree endeksi, integerbenzer bir sütun için genellikle daha hızlıdır id_phi. Ayrıca küçük ve ucuzdur. Ancak sorgu muhtemelen birleştirilmiş endeks ile genel olarak daha hızlı olacaktır.

  • Tablo tanımınızı değiştirin veya bir ifade dizini kullanın .

  • Eldeki çok noktalı virgül GiST endeksi için btree_gist, operatör sınıflarını bir içerir integer.

Triecta! Çok noktalı bir fonksiyonel GiST indeksi :

CREATE EXTENSION IF NOT EXISTS btree_gist;  -- if not installed, yet

CREATE INDEX idx_time_limits_funky ON time_limits USING gist
(id_phi, tsrange(start_date_time, end_date_time, '[]'));

Kullanım operatörü "menzil içeriyor"@> şimdi sorguda:

SELECT *
FROM   time_limits
WHERE  id_phi = 0
AND    tsrange(start_date_time, end_date_time, '[]')
    @> tsrange('2010-08-08 00:00', '2010-08-08 00:05', '[]')

Postgres 9.3+ sürümündeki SP-GiST endeksi

Bir SP-GiST - indeks sorguda bu tür daha hızlı olabileceğini hariç , bu kılavuzu alıntı :

Şu anda, yalnızca B ağacı, GiST, GIN ve BRIN dizin türleri çoklu sütun dizinlerini desteklemektedir.

Postgres 12'de hala doğru.
Bir spgistdizini yalnızca (tsrange(...))ikinci bir btreedizini ile birleştirmeniz gerekir (id_phi). Ek yükü ile bunun rekabet edebileceğinden emin değilim.
Sadece bir tsrangesütun için bir kriter ile ilgili cevap :


78
Bunu en az bir kere söylemeliyim ki, SO ve DBA ile ilgili cevaplarınızın her birinin gerçekten yüksek katma değere sahip olduğunu ve çoğu zaman en tamamlayıcı olduğunu söylemeliyim . Sadece bir kere söylemek: Saygı!
Stephane Rolland

1
Merci bien! :) Peki daha hızlı sonuç aldınız mı?
Erwin Brandstetter

Yoğun bir şekilde garip bir sorgudan oluşturulan büyük toplu kopyayı bitirmeme izin vermek zorunda kaldım, bu yüzden süreci gerçekten yavaşlatıyor, soruyu sormadan önce saatlerce dönüyordu. Ama hesapladım ve sabaha kadar dönmesine izin vermeye karar verdim, bitecek ve yeni masa yarın dolmaya hazır. Dizininizi eşzamanlı olarak iş sırasında oluşturmaya çalıştım, ancak çok fazla erişim nedeniyle (sanırım), dizinin oluşturulması kilitlenmeli. Bu aynı test zamanını tekrar çözümünüzle tekrar etmek için tekrarlayacağım. Ayrıca debian / ubuntu için nasıl 9.2 ;-)'e yükseltildiğine baktım.
Stephane Rolland,

2
@StephaneRolland: Sorgulamayı 40 saniyeden uzun süren bir süre için izlemede neden açıklamalı analiz çıktısının 45 milisaniye gösterdiği hala ilginç olabilir.
a_horse_with_no_name

1
@John: Postgresler bir dizini ileri veya geri hareket ettirebilir, ancak aynı taramada yön değiştiremez. İdeal olarak, önce (veya sonda) düğüm başına tüm nitelendirici satırlara sahip olursunuz, ancak en iyi sonucu alabilmek için tüm sütunlar için aynı hizalama (eşleşen sorgu tahminleri) olmalıdır.
Erwin Brandstetter,

5

Erwin'in cevabı zaten kapsamlı, ancak:

Zaman damgaları için aralık türleri PostgreSQL 9.1'de Jeff Davis'in Geçici uzantısı ile mevcuttur: https://github.com/jeff-davis/PostgreSQL-Temporal

Not: sınırlı özelliklere sahiptir (Timestamptz kullanır ve yalnızca '[)' stilinin üst üste binmesine izin verebilirsiniz). Ayrıca, PostgreSQL 9.2'ye yükseltmek için birçok harika neden var.


3

Çok noktalı virgül dizinini farklı bir sırayla oluşturmayı deneyebilirsiniz:

primary key(id_phi, start_date_time,end_date_time);

Ben de bir kez çok-kutuplu bir endekste endekslerin sıralanmasına ilişkin benzer bir soru yayınladım . Anahtar, arama alanını azaltmak için önce en kısıtlayıcı koşulları kullanmaya çalışıyor.

Düzenleme : Benim hatam. Şimdi görüyorum ki, zaten bu indeksi tanımlamışsınız.


Zaten her iki dizine de sahibim. Birincil anahtar hariç, diğeridir, ancak önerdiğiniz dizin zaten var ve açıklamaya bakarsanız kullanılan Bitmap Index Scan on idx_time_limits_phi_start_end
kod

1

Hızla artmayı başardım (1 saniyeden 70ms'ye)

Birçok ölçümün toplandığı bir tablom var ve birçok seviyede ( lsütun) (30s, 1m, 1h, vb.) İki aralık sütunu vardır: $sbaşlangıç ​​ve $ebitiş için.

İki çok noktalı virgül indeksi oluşturdum: biri başlangıç, diğeri bitiş için.

Seçim sorgusunu ayarladım: başlangıç ​​sınırlarının belirli bir aralıkta olduğu aralıkları seçin. ayrıca, bitiş sınırlarının verilen aralıkta olduğu aralıkları seçin.

Açıkla, dizinlerimizi verimli bir şekilde kullanarak iki satır akışını gösterir.

Endeksler:

drop index if exists agg_search_a;
CREATE INDEX agg_search_a
ON agg (measurement_id, l, "$s");

drop index if exists agg_search_b;
CREATE INDEX agg_search_b
ON agg (measurement_id, l, "$e");

Sorgu seç:

select "$s", "$e", a, t, b, c from agg
where 
    measurement_id=0 
    and l =  '30s'
    and (
        (
            "$s" > '2013-05-01 02:05:05'
            and "$s" < '2013-05-01 02:18:15'
        )
        or 
        (
             "$e" > '2013-05-01 02:00:05'
            and "$e" < '2013-05-01 02:18:05'
        )
    )

;

Açıklamak:

[
  {
    "Execution Time": 0.058,
    "Planning Time": 0.112,
    "Plan": {
      "Startup Cost": 10.18,
      "Rows Removed by Index Recheck": 0,
      "Actual Rows": 37,
      "Plans": [
    {
      "Startup Cost": 10.18,
      "Actual Rows": 0,
      "Plans": [
        {
          "Startup Cost": 0,
          "Plan Width": 0,
          "Actual Rows": 26,
          "Node Type": "Bitmap Index Scan",
          "Index Cond": "((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$s\" > '2013-05-01 02:05:05'::timestamp without time zone) AND (\"$s\" < '2013-05-01 02:18:15'::timestamp without time zone))",
          "Plan Rows": 29,
          "Parallel Aware": false,
          "Actual Total Time": 0.016,
          "Parent Relationship": "Member",
          "Actual Startup Time": 0.016,
          "Total Cost": 5,
          "Actual Loops": 1,
          "Index Name": "agg_search_a"
        },
        {
          "Startup Cost": 0,
          "Plan Width": 0,
          "Actual Rows": 36,
          "Node Type": "Bitmap Index Scan",
          "Index Cond": "((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$e\" > '2013-05-01 02:00:05'::timestamp without time zone) AND (\"$e\" < '2013-05-01 02:18:05'::timestamp without time zone))",
          "Plan Rows": 39,
          "Parallel Aware": false,
          "Actual Total Time": 0.011,
          "Parent Relationship": "Member",
          "Actual Startup Time": 0.011,
          "Total Cost": 5.15,
          "Actual Loops": 1,
          "Index Name": "agg_search_b"
        }
      ],
      "Node Type": "BitmapOr",
      "Plan Rows": 68,
      "Parallel Aware": false,
      "Actual Total Time": 0.027,
      "Parent Relationship": "Outer",
      "Actual Startup Time": 0.027,
      "Plan Width": 0,
      "Actual Loops": 1,
      "Total Cost": 10.18
    }
      ],
      "Exact Heap Blocks": 1,
      "Node Type": "Bitmap Heap Scan",
      "Plan Rows": 68,
      "Relation Name": "agg",
      "Alias": "agg",
      "Parallel Aware": false,
      "Actual Total Time": 0.037,
      "Recheck Cond": "(((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$s\" > '2013-05-01 02:05:05'::timestamp without time zone) AND (\"$s\" < '2013-05-01 02:18:15'::timestamp without time zone)) OR ((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$e\" > '2013-05-01 02:00:05'::timestamp without time zone) AND (\"$e\" < '2013-05-01 02:18:05'::timestamp without time zone)))",
      "Lossy Heap Blocks": 0,
      "Actual Startup Time": 0.033,
      "Plan Width": 44,
      "Actual Loops": 1,
      "Total Cost": 280.95
    },
    "Triggers": []
  }
]

İşin püf noktası, plan düğümlerinizin yalnızca istenen satırları içermesidir. Önceden, binlerce düğüm plan düğümünde seçildi çünkü seçildi all points from some point in time to the very end, ardından sonraki düğüm gereksiz satırları kaldırdı.

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.