Ukur ukuran baris tabel PostgreSQL

83

Saya punya tabel PostgreSQL. select *sangat lambat sedangkan select idbagus dan cepat. Saya pikir mungkin ukuran barisnya sangat besar dan butuh beberapa saat untuk transportasi, atau mungkin ada beberapa faktor lain.

Saya membutuhkan semua bidang (atau hampir semuanya), jadi memilih hanya sebagian bukan perbaikan cepat. Memilih bidang yang saya inginkan masih lambat.

Berikut skema tabel saya minus namanya:

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                  | 

Ukuran kolom teks bisa berapa saja. Tapi tetap saja, tidak lebih dari beberapa kilobyte dalam kasus terburuk.

Pertanyaan

  1. Apakah ada sesuatu tentang ini yang berteriak 'tidak efisien gila'?
  2. Apakah ada cara untuk mengukur ukuran halaman di baris perintah Postgres untuk membantu saya men-debug ini?
Joe
sumber
Sebenarnya ... salah satu kolom adalah 11 MB. Itu akan menjelaskannya saya pikir. Jadi adakah cara untuk melakukannya, length(*)bukan hanya length(field)? Saya tahu itu bukan byte byte tetapi saya hanya perlu nilai sekitar.
Joe

Jawaban:

101

Q2: way to measure page size

PostgreSQL menyediakan sejumlah Fungsi Ukuran Objek Basis Data . Saya mengemas yang paling menarik dalam kueri ini dan menambahkan beberapa Fungsi Akses Statistik di bagian bawah. (Modul pgstattuple tambahan menyediakan fungsi yang lebih bermanfaat.)

Ini akan menunjukkan bahwa metode yang berbeda untuk mengukur "ukuran baris" mengarah ke hasil yang sangat berbeda. Itu semua tergantung pada apa yang ingin Anda ukur, tepatnya.

Permintaan ini membutuhkan Postgres 9.3 atau lebih baru . Untuk versi yang lebih lama lihat di bawah.

Menggunakan VALUESekspresi dalam LATERALsubquery , untuk menghindari mengeja perhitungan untuk setiap baris.

Ganti public.tbl(dua kali) dengan nama tabel yang memenuhi syarat untuk skema Anda untuk mendapatkan tampilan yang kompak dari statistik yang dikumpulkan tentang ukuran baris Anda. Anda bisa membungkus ini menjadi fungsi plpgsql untuk penggunaan berulang, masukkan nama tabel sebagai parameter dan gunakan 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);

Hasil:

              metrik | byte / ct | bytes_pretty | bytes_per_row
----------------------------------- + ---------- + --- ----------- + ---------------
 core_relation_size | 44138496 | 42 MB | 91
 visibility_map | 0 | 0 byte | 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 | |

Untuk versi yang lebih lama (Postgres 9.2 atau lebih lama):

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;

Hasil yang sama

Q1: anything inefficient?

Anda dapat mengoptimalkan pesanan kolom untuk menghemat beberapa byte per baris, yang saat ini terbuang untuk pelurusan pelurusan:

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                  |

Ini menghemat antara 8 dan 18 byte per baris. Saya menyebutnya "kolom tetris" . Detail:

Juga pertimbangkan:

Erwin Brandstetter
sumber
Cuplikan pre 9.3 Anda melempar divisi dengan nol jika tabelnya kosong. Saya sebenarnya ingin menggunakan versi 9.3+, tetapi salah memilih yang salah dan harus menghabiskan beberapa jam untuk memperbaikinya ... Sekarang saya tidak bisa membiarkan semua waktu itu sia-sia. Ganti , unnest(val) / ctdengan , (LEAST(unnest(val), unnest(val) * ct)) / (ct - 1 + sign(ct))dan tidak akan membuang. Dasar pemikirannya adalah bahwa, kapan ctpun 0, valakan digantikan oleh 0dan ctakan digantikan oleh 1.
GuiRitter
1
@ GuiRitter: Terima kasih telah menunjukkan. Saya menerapkan perbaikan yang lebih sederhana. Juga ada beberapa pembaruan umum saat melakukannya - tetapi kueri tetap sama.
Erwin Brandstetter
35

Perkiraan ukuran baris, termasuk konten ed TOAST , mudah didapat dengan menanyakan panjang representasi TEXT dari seluruh baris:

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

Ini adalah perkiraan dekat dengan jumlah byte yang akan diambil sisi klien saat menjalankan:

SELECT * FROM tablename WHERE primary_key=:value;

... dengan asumsi bahwa pemanggil permintaan meminta hasil dalam format teks, yang merupakan apa yang dilakukan sebagian besar program (format biner dimungkinkan, tetapi itu tidak sebanding dengan masalah dalam kebanyakan kasus).

Teknik yang sama dapat diterapkan untuk menemukan baris N"terbesar dalam teks" dari tablename:

SELECT primary_key, octet_length(t.*::text) FROM tablename AS t
   ORDER BY 2 DESC LIMIT :N;
Daniel Vérité
sumber
Cara terbaik untuk mendapatkan perkiraan dengan cepat saat bekerja dengan data besar (mis. Mayoritas ukuran baris terletak pada kolom yang disimpan bersulang dengan panjang variabel), ide bagus!
fgblomqvist
hasilnya byte?
Akmal Salikhov
14

Ada beberapa hal yang bisa terjadi. Secara umum, saya ragu bahwa panjang adalah masalah proksimal. Saya menduga Anda memiliki masalah terkait panjang.

Anda mengatakan bidang teks bisa mencapai beberapa k. Baris tidak dapat melebihi 8k dalam penyimpanan utama, dan kemungkinan bidang teks Anda yang lebih besar telah dipanggang , atau dipindahkan dari penyimpanan utama ke penyimpanan yang diperluas dalam file yang terpisah. Ini membuat penyimpanan utama Anda lebih cepat (jadi pilih id sebenarnya lebih cepat karena lebih sedikit halaman disk untuk diakses) tetapi pilih * menjadi lebih lambat karena ada I / O yang lebih acak.

Jika total ukuran baris Anda masih di bawah 8k, Anda dapat mencoba mengubah pengaturan penyimpanan. Namun, saya akan memperingatkan bahwa Anda bisa mendapatkan hal-hal buruk terjadi ketika memasukkan atribut besar ke penyimpanan utama, jadi sebaiknya jangan sentuh ini jika Anda tidak perlu dan jika Anda melakukannya, tetapkan batas yang tepat melalui kendala pemeriksaan. Jadi transportasi sepertinya bukan satu-satunya. Mungkin menyusun banyak, banyak bidang yang memerlukan pembacaan acak. Sejumlah besar pembacaan acak juga dapat menyebabkan cache meleset, dan sejumlah besar memori yang dibutuhkan dapat mengharuskan hal-hal terwujud pada disk dan sejumlah besar baris lebar, jika bergabung ada (dan ada satu jika TOAST terlibat) mungkin memerlukan lebih mahal bergabung dengan pola, dll.

Hal pertama yang akan saya lakukan adalah memilih lebih sedikit baris dan melihat apakah itu membantu. Jika itu berhasil, Anda dapat mencoba menambahkan lebih banyak RAM ke server juga, tetapi saya akan mulai dan melihat di mana kinerja mulai jatuh karena perubahan rencana dan cache gagal terlebih dahulu.

Chris Travers
sumber
4

Menggunakan Fungsi Ukuran Objek Basis Data yang disebutkan di atas:

SELECT primary_key, pg_column_size(tablename.*) FROM tablename;

WhiteFire Sondergaard
sumber
tampak menjanjikan, tetapi untuk alasan apa pun itu tidak berhasil dalam kasus saya. pg_column_size (tablename.big_column) melebihi nilai pg_column_size (tablename. *)
linqu