วัดขนาดของแถวตาราง PostgreSQL


83

ฉันมีตาราง PostgreSQL select *ช้ามากในขณะที่select idดีและรวดเร็ว ฉันคิดว่าอาจเป็นไปได้ว่าขนาดของแถวนั้นใหญ่มากและใช้เวลาในการขนส่งสักครู่หรืออาจเป็นปัจจัยอื่น

ฉันต้องการฟิลด์ทั้งหมด (หรือเกือบทั้งหมด) ดังนั้นการเลือกเฉพาะเซ็ตย่อยไม่ใช่การแก้ไขด่วน การเลือกเขตข้อมูลที่ฉันต้องการยังคงช้า

นี่คือคีคีโต๊ะของฉันลบชื่อ:

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                  | 

ขนาดของฟิลด์ข้อความอาจมีขนาดใดก็ได้ แต่ก็ยังไม่เกินสองสามกิโลไบต์ในกรณีที่เลวร้ายที่สุด

คำถาม

  1. มีอะไรเกี่ยวกับเรื่องนี้ที่ส่งเสียงว่า
  2. มีวิธีการวัดขนาดหน้าของบรรทัดคำสั่ง Postgres เพื่อช่วยในการแก้ปัญหาหรือไม่

จริงๆแล้ว ... หนึ่งในคอลัมน์คือ 11 MB ฉันจะอธิบายอย่างนั้น ดังนั้นมีวิธีการทำlength(*)มากกว่าเพียงแค่length(field)? ฉันรู้ว่าตัวอักษรไม่ใช่ไบต์ แต่ฉันต้องการเพียงค่าโดยประมาณ
Joe

คำตอบ:


101

Q2: way to measure page size

PostgreSQL มีฟังก์ชันขนาดวัตถุฐานข้อมูลจำนวนมาก ฉันได้รวบรวมสิ่งที่น่าสนใจที่สุดในแบบสอบถามนี้และเพิ่มฟังก์ชันการเข้าถึงสถิติที่ด้านล่าง (โมดูลเพิ่มเติมpgstattupleให้ฟังก์ชันที่มีประโยชน์มากขึ้น)

นี่จะแสดงให้เห็นว่าวิธีต่างๆในการวัด "ขนาดของแถว" นำไปสู่ผลลัพธ์ที่แตกต่างกันมาก ทุกอย่างขึ้นอยู่กับสิ่งที่คุณต้องการวัด

แบบสอบถามนี้ต้องPostgres 9.3 หรือในภายหลัง สำหรับรุ่นเก่าดูด้านล่าง

ใช้VALUESนิพจน์ในLATERALแบบสอบถามย่อยเพื่อหลีกเลี่ยงการสะกดคำการคำนวณสำหรับทุกแถว

แทนที่public.tbl(สองครั้ง) ด้วยชื่อตารางที่ผ่านการรับรองด้วยสคีมาของคุณเพื่อรับมุมมองแบบย่อของสถิติที่รวบรวมไว้เกี่ยวกับขนาดของแถวของคุณ คุณสามารถห่อสิ่งนี้ลงในฟังก์ชั่น plpgsql สำหรับการใช้งานซ้ำ ๆ ส่งชื่อตารางเป็นพารามิเตอร์และใช้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);

ผลลัพธ์:

              เมตริก | bytes / ct | bytes_pretty | bytes_per_row
----------------------------------- + + --- ---------- ----------- + ---------------
 core_relation_size | 44138496 | 42 MB | 91
 ทัศนวิสัย | 0 | 0 ไบต์ 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 | |

สำหรับรุ่นเก่ากว่า (Postgres 9.2 หรือเก่ากว่า):

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;

ผลลัพธ์เดียวกัน

Q1: anything inefficient?

คุณสามารถปรับลำดับคอลัมน์ให้เหมาะสมเพื่อบันทึกบางไบต์ต่อแถวปัจจุบันสูญเสียไปกับการจัดตำแหน่งการเติมเต็ม:

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                  |

วิธีนี้ช่วยประหยัดระหว่าง 8 ถึง 18 ไบต์ต่อแถว ผมเรียกมันว่า"Tetris คอลัมน์" รายละเอียด:

พิจารณาด้วย:


ตัวอย่างข้อมูลของคุณก่อน 9.3 โยนส่วนด้วยศูนย์ถ้าตารางว่างเปล่า จริง ๆ แล้วฉันต้องการใช้เวอร์ชัน 9.3 แต่เลือกผิดโดยไม่ได้ตั้งใจและต้องใช้เวลาแก้ไขสองสามชั่วโมง ... ตอนนี้ฉันไม่สามารถปล่อยให้เสียเวลา แทนที่, unnest(val) / ctด้วย, (LEAST(unnest(val), unnest(val) * ct)) / (ct - 1 + sign(ct))และมันจะไม่โยน เหตุผลก็คือว่าเมื่อctเป็น0, valจะถูกแทนที่ด้วย0และจะถูกแทนที่ด้วยct 1
GuiRitter

1
@GuiRitter: ขอบคุณที่ชี้ให้เห็น ฉันใช้การแก้ไขที่ง่ายกว่าแม้ว่า นอกจากนี้ยังมีการปรับปรุงทั่วไปบางอย่างในขณะที่อยู่ในนั้น - แต่แบบสอบถามยังคงเหมือนเดิม
Erwin Brandstetter

35

การประมาณขนาดของแถวรวมถึงเนื้อหาของTOASTนั้นง่ายต่อการรับโดยการสอบถามความยาวของการแทนค่า TEXT ของทั้งแถว:

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

นี่เป็นการประมาณใกล้ถึงจำนวนไบต์ที่จะดึงข้อมูลฝั่งไคลเอ็นต์เมื่อเรียกใช้งาน:

SELECT * FROM tablename WHERE primary_key=:value;

... สมมติว่าผู้เรียกของแบบสอบถามกำลังร้องขอผลลัพธ์ในรูปแบบข้อความซึ่งเป็นสิ่งที่โปรแกรมส่วนใหญ่ทำ (รูปแบบไบนารีเป็นไปได้ แต่ไม่คุ้มกับปัญหาในกรณีส่วนใหญ่)

สามารถใช้เทคนิคเดียวกันนี้เพื่อค้นหาแถวN"ที่ใหญ่ที่สุดในข้อความ" ของtablename:

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

วิธีที่ยอดเยี่ยมในการรับการประมาณค่าอย่างรวดเร็วเมื่อทำงานกับข้อมูลขนาดใหญ่ (เช่นขนาดแถวส่วนใหญ่อยู่ในคอลัมน์ที่จัดเก็บขนมปังปิ้งที่มีความยาวผันแปรได้) ความคิดที่ดี!
fgblomqvist

14

มีบางสิ่งที่อาจเกิดขึ้นได้ โดยทั่วไปแล้วฉันสงสัยว่าความยาวเป็นปัญหาที่ใกล้เคียงกัน ฉันสงสัยว่าคุณมีปัญหาเกี่ยวกับความยาว

คุณบอกว่าเขตข้อมูลข้อความสามารถเพิ่มได้ไม่กี่ k แถวไม่สามารถมีพื้นที่เก็บข้อมูลหลักเกินกว่า 8k และเป็นไปได้ว่าช่องข้อความขนาดใหญ่ของคุณได้รับการปิ้งหรือย้ายออกจากที่เก็บข้อมูลหลักไปยังที่เก็บข้อมูลเพิ่มเติมในไฟล์แยกต่างหาก สิ่งนี้ทำให้ที่เก็บข้อมูลหลักของคุณเร็วขึ้น (ดังนั้นการเลือกรหัสจริงเร็วกว่าเนื่องจากการเข้าถึงหน้าดิสก์น้อยลง) แต่การเลือก * จะช้าลงเนื่องจากมี I / O แบบสุ่มมากขึ้น

หากขนาดแถวทั้งหมดของคุณยังคงต่ำกว่า 8k คุณสามารถลองเปลี่ยนการตั้งค่าการจัดเก็บ อย่างไรก็ตามฉันจะเตือนว่าคุณสามารถได้รับสิ่งที่ไม่ดีเกิดขึ้นเมื่อใส่แอททริบิวต์ที่มีขนาดใหญ่ลงในที่เก็บข้อมูลหลักดังนั้นอย่าแตะต้องสิ่งนี้หากคุณไม่จำเป็นต้องตั้งค่าขีด จำกัด ที่เหมาะสมผ่านข้อ จำกัด การตรวจสอบ ดังนั้นการขนส่งไม่น่าจะเป็นสิ่งเดียว มันอาจจะทำการเรียงหลาย ๆ ฟิลด์ที่จำเป็นต้องมีการอ่านแบบสุ่ม การอ่านแบบสุ่มจำนวนมากอาจทำให้แคชหายไปและหน่วยความจำจำนวนมากจำเป็นต้องมีสิ่งที่ต้องเกิดขึ้นบนดิสก์และแถวกว้างจำนวนมากหากมีการเข้าร่วม (และมีหนึ่งหากเกี่ยวข้องกับ TOAST) อาจต้องเสียค่าใช้จ่าย เข้าร่วมรูปแบบ ฯลฯ

สิ่งแรกที่ฉันจะทำคือเลือกแถวให้น้อยลงและดูว่ามันช่วยได้ไหม หากใช้งานได้คุณสามารถลองเพิ่ม RAM ให้กับเซิร์ฟเวอร์ได้เช่นกัน แต่ฉันจะเริ่มและดูว่าประสิทธิภาพการทำงานเริ่มลดลงเนื่องจากการเปลี่ยนแปลงแผนและแคชหายไปก่อน


4

การใช้ฟังก์ชั่นขนาดวัตถุฐานข้อมูลที่กล่าวถึงข้างต้น:

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


ดูมีแนวโน้ม แต่ด้วยเหตุผลใดก็ตามมันไม่ทำงานในกรณีของฉัน pg_column_size (tablename.big_column) มีค่าเกินกว่า pg_column_size (tablename. *)
linqu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.