ฉันจะแยก ctid ลงในหมายเลขหน้าและแถวได้อย่างไร


16

แต่ละแถวในตารางมีคอลัมน์ระบบ ctidประเภทtidที่แสดงถึงตำแหน่งทางกายภาพของแถว:

create table t(id serial);
insert into t default values;
insert into t default values;
select ctid
     , id
from t;
ctid | รหัส
: ---- | -:
(0,1) | 1
(0,2) | 2

dbfiddle ที่นี่

อะไรคือวิธีที่ดีที่สุดในการได้รับเพียงจำนวนหน้าเป็นจากctidในประเภทที่เหมาะสมที่สุด (เช่นinteger, bigintหรือnumeric(1000,0))?

วิธีเดียวที่ฉันสามารถคิดเป็นน่าเกลียดมาก


1
IIRC เป็นรูปแบบเวกเตอร์และเราไม่มีวิธีการเข้าถึงสำหรับสิ่งเหล่านี้ ฉันไม่แน่ใจว่าคุณสามารถทำได้จากฟังก์ชั่น C หรือไม่ เครกจะบอกแน่นอน :)
Dezso

2
คุณสามารถคัดเลือกเป็น POINT ได้หรือไม่? เช่น. select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma

1
ชื่อแนะนำให้คุณหลังจากทั้งหมายเลขหน้าและดัชนี tupleหลังจากนั้นคุณจะแคบลงจนถึงหมายเลขหน้า ฉันไปกับรุ่นในร่างกายดัชนี tuple เป็นส่วนเสริมเล็กน้อย
Erwin Brandstetter

คำตอบ:


21
SELECT (ctid::text::point)[0]::bigint AS page_number FROM t;

ซอของคุณด้วยโซลูชันของฉัน

@bma พูดถึงสิ่งที่คล้ายกันในความคิดเห็นแล้ว นี่คือ ...

เหตุผลสำหรับประเภท

ctidเป็นประเภทtid(tuple identifier) ​​เรียกว่าItemPointerในรหัส C ตามเอกสาร:

ctidนี้เป็นชนิดข้อมูลของคอลัมน์ระบบ tuple ID คือคู่ ( หมายเลขบล็อก , ดัชนี tuple ภายในบล็อก ) ที่ระบุตำแหน่งทางกายภาพของแถวภายในตาราง

เหมืองเน้นหนัก และ:

( ItemPointerหรือที่เรียกว่าCTID)

บล็อกคือ8 KBในการติดตั้งมาตรฐาน ขนาดโต๊ะได้สูงสุด32 TB ตามด้วยเหตุผลว่าหมายเลขบล็อกต้องมีอย่างน้อยสูงสุด (การคำนวณแก้ไขตามความคิดเห็นโดย @Daniel):

SELECT (2^45 / 2^13)::int      -- = 2^32 = 4294967294

integerซึ่งจะพอดีไม่ได้ลงนาม ในการตรวจสอบเพิ่มเติมฉันพบในซอร์สโค้ดที่ ...

บล็อกจะมีหมายเลขลำดับ0 ถึง 0xFFFFFFFE

เหมืองเน้นหนัก ซึ่งยืนยันการคำนวณครั้งแรก:

SELECT 'xFFFFFFFE'::bit(32)::int8 -- max page number: 4294967294

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

โพลล์

มีการโยนไม่มีการลงทะเบียนสำหรับtidการพิมพ์ใน Postgres 9.3:

SELECT *
FROM   pg_cast
WHERE  castsource = 'tid'::regtype
OR     casttarget = 'tid'::regtype;

 castsource | casttarget | castfunc | castcontext | castmethod
------------+------------+----------+-------------+------------
(0 rows)

textคุณยังสามารถส่งข้อมูลไปยัง มีการแสดงข้อความสำหรับทุกสิ่งใน Postgres :

อีกข้อยกเว้นที่สำคัญก็คือว่า "บรรยากาศการแปลงฉันอัตโนมัติ / O" ผู้ดำเนินการโดยใช้ชนิดข้อมูลของตัวเองฟังก์ชั่น I / O แปลงไปยังหรือจากข้อความหรือประเภทสตริงอื่น ๆ pg_castที่ไม่ได้เป็นตัวแทนอย่างชัดเจนใน

การแทนข้อความตรงกับจุดซึ่งประกอบด้วยfloat8ตัวเลขสองตัว

คุณสามารถเข้าถึงหมายเลขแรกของจุดที่มีค่าดัชนี 0. Cast bigintมา voila

ประสิทธิภาพ

ฉันทำการทดสอบอย่างรวดเร็วบนโต๊ะที่มีแถว 30k (ดีที่สุด 5 รายการ) จากการแสดงทางเลือกสองทางที่นึกถึงรวมถึงต้นฉบับของคุณ:

SELECT (ctid::text::point)[0]::int                              --  25 ms
      ,right(split_part(ctid::text, ',', 1), -1)::int           --  28 ms
      ,ltrim(split_part(ctid::text, ',', 1), '(')::int          --  29 ms
      ,(ctid::text::t_tid).page_number                          --  31 ms
      ,(translate(ctid::text,'()', '{}')::int[])[1]             --  45 ms
      ,(replace(replace(ctid::text,'(','{'),')','}')::int[])[1] --  51 ms
      ,substring(right(ctid::text, -1), '^\d+')::int            --  52 ms
      ,substring(ctid::text, '^\((\d+),')::int                  -- 143 ms
FROM tbl;

intแทนที่จะเป็นbigintที่นี่ส่วนใหญ่ไม่เกี่ยวข้องกับวัตถุประสงค์ของการทดสอบ bigintผมไม่ได้ทำซ้ำ คาสต์
เพื่อt_tidสร้างบนคอมโพสิตประเภทที่ผู้ใช้กำหนดเช่น @Jake แสดงความคิดเห็น
ส่วนสำคัญของมัน: การคัดเลือกนักแสดงมีแนวโน้มที่จะเร็วกว่าการจัดการสตริง การแสดงออกปกติมีราคาแพง วิธีแก้ปัญหาข้างต้นนั้นสั้นที่สุดและเร็วที่สุด


1
ขอบคุณเออร์วินสิ่งที่มีประโยชน์ จากที่นี่ดูเหมือนว่าctidมี 6 ไบต์กับ 4 สำหรับหน้าและ 2 สำหรับแถว ฉันเป็นห่วงเรื่องการคัดเลือกนักแสดงfloatแต่ฉันคิดว่าฉันไม่ต้องการสิ่งที่คุณพูดที่นี่ ดูเหมือนว่าคอมโพสิตที่ผู้ใช้กำหนดจะช้ากว่าการใช้งานpointมากคุณจะพบสิ่งนั้นด้วยหรือไม่
Jack Douglas

@JackDouglas: bigintเมื่อตรวจสอบต่อไปผมได้ลดลงกลับไป พิจารณาการอัพเดท
Erwin Brandstetter

1
@ JackDouglas: ฉันชอบความคิดของคุณเกี่ยวกับนักแสดงในคอมโพสิต มันสะอาดและทำงานได้ดีมาก - แม้ว่าการโยนไปpointและกลับไปint8ยังเร็วกว่า) การส่งไปยังประเภทที่กำหนดไว้ล่วงหน้าจะเร็วขึ้นเล็กน้อย ฉันเพิ่มลงในการทดสอบของฉันเพื่อเปรียบเทียบ ฉันจะทำให้(page_number bigint, row_number integer)แน่ใจ
Erwin Brandstetter

1
2^40มีเพียง 1TB ไม่ใช่ 32TB ซึ่ง2^45แบ่งตาม2^13ให้2^32ดังนั้นเต็ม 32 บิตจึงจำเป็นสำหรับหมายเลขหน้า
Daniel Vérité

1
อีกอย่างที่ควรค่าแก่การสังเกตคือpg_freespacemapใช้bigintสำหรับ blkno
แจ็คดักลาส
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.