PostgreSQL tablo satırının boyutunu ölçün


83

Bir PostgreSQL tablom var. select *Oysa çok yavaş select id, güzel ve hızlı. Sanırım satırın büyüklüğü çok büyük olabilir ve taşınması biraz zaman alabilir ya da başka bir faktör olabilir.

Tüm alanlara (veya neredeyse hepsine) ihtiyacım var, bu yüzden sadece bir altküme seçmek hızlı bir düzeltme değildir. İstediğim alanları seçmek hala yavaş.

İşte benim tablo şemem eksi isimleri:

integer                  | not null default nextval('core_page_id_seq'::regclass)
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
integer                  | not null default 0
text                     | default '{}'::text
text                     | 
timestamp with time zone | 
integer                  | 
timestamp with time zone | 
integer                  | 

Metin alanının boyutu herhangi bir boyutta olabilir. Fakat yine de, en kötü durumda birkaç kilobayttan fazla değil.

Sorular

  1. Bu konuda 'çılgın verimsiz' diye çığlık atan bir şey var mı?
  2. Bu hata ayıklamama yardımcı olmak için Postgres komut satırında sayfa boyutunu ölçmenin bir yolu var mı?

Aslında ... sütunlardan biri 11 MB. Bu açıklayacağımı düşünüyorum. Yani yapmak için bir yol yoktur length(*)ziyade sadece daha length(field)? Bunun karakterlerin bayt olmadığını biliyorum ama sadece yaklaşık bir değere ihtiyacım var.
Joe,

Yanıtlar:


101

S2: way to measure page size

PostgreSQL, bir dizi Veritabanı Nesnesi Boyutu Fonksiyonu sunar . Bu sorgudaki en ilginç olanları topladım ve en altına bazı İstatistik Erişim İşlevleri ekledim . (Ek modül pgstattuple , henüz daha kullanışlı fonksiyonlar sağlar.)

Bu, "satırın boyutunu" ölçmek için kullanılan farklı yöntemlerin çok farklı sonuçlara yol açtığını gösterecektir. Her şey tam olarak ne ölçmek istediğinize bağlıdır.

Bu sorgu Postgres 9.3 veya daha yenisini gerektirir . Eski versiyonlar için aşağıya bakınız.

Her satır için hesaplamaları heceleyerek önlemek VALUESiçin LATERALalt sorguda bir ifade kullanma .

public.tblSatırlarınızın büyüklüğü hakkında toplanan istatistiklerin kompakt bir görünümünü elde etmek için isteğe bağlı olarak şemaya uygun tablo adınızla değiştirin (iki kez). Bunu tekrar tekrar kullanmak için bir plpgsql işlevine sarabilir, tablo adını parametre olarak girebilir ve EXECUTE...

SELECT l.metric, l.nr AS "bytes/ct"
     , CASE WHEN is_size THEN pg_size_pretty(nr) END AS bytes_pretty
     , CASE WHEN is_size THEN nr / NULLIF(x.ct, 0) END AS bytes_per_row
FROM  (
   SELECT min(tableoid)        AS tbl      -- = 'public.tbl'::regclass::oid
        , count(*)             AS ct
        , sum(length(t::text)) AS txt_len  -- length in characters
   FROM   public.tbl t                     -- provide table name *once*
   ) x
 , LATERAL (
   VALUES
      (true , 'core_relation_size'               , pg_relation_size(tbl))
    , (true , 'visibility_map'                   , pg_relation_size(tbl, 'vm'))
    , (true , 'free_space_map'                   , pg_relation_size(tbl, 'fsm'))
    , (true , 'table_size_incl_toast'            , pg_table_size(tbl))
    , (true , 'indexes_size'                     , pg_indexes_size(tbl))
    , (true , 'total_size_incl_toast_and_indexes', pg_total_relation_size(tbl))
    , (true , 'live_rows_in_text_representation' , txt_len)
    , (false, '------------------------------'   , NULL)
    , (false, 'row_count'                        , ct)
    , (false, 'live_tuples'                      , pg_stat_get_live_tuples(tbl))
    , (false, 'dead_tuples'                      , pg_stat_get_dead_tuples(tbl))
   ) l(is_size, metric, nr);

Sonuç:

              metrik | bayt / ct | bytes_pretty | bytes_per_row
----------------------------------- + ---------- + --- ----------- + ---------------
 core_relation_size | 44138496 | 42 MB | 91
 visibility_map | 0 | 0 bayt | 0
 free_space_map | 32768 | 32 kB | 0
 table_size_incl_toast | 44179456 | 42 MB | 91
 indexes_size | 33128448 | 32 MB | 68
 total_size_incl_toast_and_indexes | 77307904 | 74 MB | 159
 live_rows_in_text_representation | 29987360 | 29 MB | 62
 ------------------------------ | | |
 row_count | 483424 | |
 live_tuples | 483424 | |
 dead_tuples | 2677 | |

Daha eski sürümler için (Postgres 9.2 veya üstü):

WITH x AS (
   SELECT count(*)               AS ct
        , sum(length(t::text))   AS txt_len  -- length in characters
        , 'public.tbl'::regclass AS tbl      -- provide table name as string
   FROM   public.tbl t                       -- provide table name as name
   ), y AS (
   SELECT ARRAY [pg_relation_size(tbl)
               , pg_relation_size(tbl, 'vm')
               , pg_relation_size(tbl, 'fsm')
               , pg_table_size(tbl)
               , pg_indexes_size(tbl)
               , pg_total_relation_size(tbl)
               , txt_len
             ] AS val
        , ARRAY ['core_relation_size'
               , 'visibility_map'
               , 'free_space_map'
               , 'table_size_incl_toast'
               , 'indexes_size'
               , 'total_size_incl_toast_and_indexes'
               , 'live_rows_in_text_representation'
             ] AS name
   FROM   x
   )
SELECT unnest(name)                AS metric
     , unnest(val)                 AS "bytes/ct"
     , pg_size_pretty(unnest(val)) AS bytes_pretty
     , unnest(val) / NULLIF(ct, 0) AS bytes_per_row
FROM   x, y

UNION ALL SELECT '------------------------------', NULL, NULL, NULL
UNION ALL SELECT 'row_count', ct, NULL, NULL FROM x
UNION ALL SELECT 'live_tuples', pg_stat_get_live_tuples(tbl), NULL, NULL FROM x
UNION ALL SELECT 'dead_tuples', pg_stat_get_dead_tuples(tbl), NULL, NULL FROM x;

Aynı sonuç.

S1: anything inefficient?

Hizalama dolgusunda boşa harcanan satır başına bazı baytları kaydetmek için sütun sırasını optimize edebilirsiniz :

integer                  | not null default nextval('core_page_id_seq'::regclass)
integer                  | not null default 0
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
text                     | default '{}'::text
text                     |
timestamp with time zone |
timestamp with time zone |
integer                  |
integer                  |

Bu satır başına 8 ila 18 bayt kaydeder. Ben buna "sütun tetris" diyorum . Detaylar:

Ayrıca şunu göz önünde bulundurun:


9.3 öncesi snippet'iniz, masa boşsa sıfırla bölme atar. Aslında 9.3+ sürümünü kullanmak istedim, ancak yanlış olanı yanlış seçtim ve birkaç saatimi tamir etmek zorunda kaldım ... Şimdi tüm zamanımın boşa gitmesine izin veremem. Değiştir , unnest(val) / cttarafından , (LEAST(unnest(val), unnest(val) * ct)) / (ct - 1 + sign(ct))ve atmak olmaz. Gerekçe ne zaman, yani ctolup 0, valyerini alacaktır 0ve ctyerini alacaktır 1.
GuiRitter

1
@GuiRitter: Gösterdiğiniz için teşekkürler. Yine de daha basit bir düzeltme yaptım. Ayrıca bazı genel güncellemeler sırasında olur - ancak sorgu aynı kalır.
Erwin Brandstetter

35

TOAST'ın içeriğini de içeren bir satırın boyutunun yaklaştırılması , tüm satırın TEXT temsilinin uzunluğunu sorgulayarak elde etmek kolaydır:

SELECT octet_length(t.*::text) FROM tablename AS t WHERE primary_key=:value;

Bu, yürütme sırasında istemci tarafından alınacak bayt sayısına yakın bir yaklaşımdır:

SELECT * FROM tablename WHERE primary_key=:value;

... sorgunun arayan kişisinin, çoğu programın yaptığı (ikili format mümkündür, ancak çoğu durumda bu sorunun değmeyeceği) metin biçiminde sonuçlar istediğini varsayarsak.

Aynı teknik, N"metindeki en büyük" satırlarını bulmak için de uygulanabilir tablename:

SELECT primary_key, octet_length(t.*::text) FROM tablename AS t
   ORDER BY 2 DESC LIMIT :N;

Büyük verilerle çalışırken hızlıca bazı tahminler almanın mükemmel bir yolu (örneğin, satır büyüklüğünün büyük kısmı değişken uzunluktaki tost saklı sütunlarda bulunur), iyi fikir!
fgblomqvist

14

Olabilecek birkaç şey var. Genel olarak, bu uzunluğun proksimal problem olduğundan şüpheliyim. Bunun yerine boy ile ilgili bir problemin olduğundan şüpheleniyorum.

Metin alanlarının birkaç k alabileceğini söylüyorsunuz. Bir satır ana depolama biriminde 8k'yi geçemez ve büyük metin alanlarınızın TOASTed olması muhtemeldir. veya ana depolama alanından ayrı dosyalar halinde genişletilmiş bir depolama alanına taşınması . Bu, ana depolama alanınızı daha hızlı hale getirir (bu nedenle daha düşük disk sayfalarına erişmek için daha az disk sayfası olması nedeniyle seçim kimliği daha hızlıdır);

Toplam satır boyutlarınız hala 8k'nin altındaysa, depolama ayarlarını değiştirmeyi deneyebilirsiniz. Ancak, ana depolamaya büyük boyutlu bir özellik eklerken kötü şeylerin olabileceği konusunda uyarırdım, bu nedenle gerekmediğinde ve bunu yaparsanız kontrol kısıtlamaları ile uygun sınırlar koyarsanız, buna dokunmamanız en iyisidir. Yani ulaşım muhtemelen tek şey değil. Rasgele okuma gerektiren birçok, birçok alanı harmanlıyor olabilir. Çok sayıda rastgele okuma aynı zamanda önbellek eksikliğine de neden olabilir ve gerekli miktarda bellek kullanılması, bir birleştirme varsa (ve eğer TOAST'ın dahil olduğu bir tane varsa) daha pahalı olmasını gerektirebilir. birleştirme kalıpları vb.

Yapacağım ilk şey daha az satır seçmek ve bunun işe yarayıp yaramadığını görmek. Bu işe yararsa, sunucuya da daha fazla RAM eklemeyi deneyebilirsiniz, ancak başlangıçta plan değişiklikleri ve önbellek özlemeleri nedeniyle performansın düşmeye başladığını göreceğim.


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.