Làm cách nào để phân tách ctid thành số trang và hàng?


16

Mỗi hàng trong một bảng có một cột hệ thống ctid của loại tidđại diện cho vị trí địa lý của hàng:

create table t(id serial);
insert into t default values;
insert into t default values;
select ctid
     , id
from t;
ctid | Tôi
: ---- | -:
(0,1) | 1
(0,2) | 2

dbfiddle đây

Cách tốt nhất để nhận được chỉ số trang, kể từ những gì ctidtrong các loại thích hợp nhất (ví dụ integer, biginthoặc numeric(1000,0))?

Cách duy nhất tôi có thể nghĩ là rất xấu xí.


1
IIRC nó là một loại vectơ và chúng tôi không có các phương thức truy cập trên các phương thức này. Tôi không chắc chắn nếu bạn có thể làm điều đó từ một chức năng C. Craig sẽ nói chắc chắn :)
dezso

2
Bạn có thể chọn làm ĐIỂM? Ví dụ. select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma

1
Tiêu đề gợi ý bạn theo sau cả số trangchỉ số tuple , sau này bạn thu hẹp về số trang. Tôi đã đi với phiên bản trong cơ thể, chỉ số tuple là một phần mở rộng tầm thường.
Erwin Brandstetter

Câu trả lời:


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

Fiddle của bạn với giải pháp của tôi.

@bma đã gợi ý một cái gì đó tương tự trong một bình luận. Đây là ...

Cơ sở lý luận cho các loại

ctidlà loại tid(định danh tuple), được gọi ItemPointertrong mã C. Mỗi tài liệu:

Đây là kiểu dữ liệu của cột hệ thống ctid. ID tuple là một cặp ( số khối , chỉ số tuple trong khối ) xác định vị trí vật lý của hàng trong bảng của nó.

Nhấn mạnh đậm của tôi. Và:

( ItemPointer, còn được gọi là CTID)

Một khối là 8 KB trong cài đặt tiêu chuẩn. Kích thước bảng tối đa là 32 TB . Theo logic, số khối phải chứa tối thiểu tối đa (tính toán cố định theo nhận xét của @Daniel):

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

Mà sẽ phù hợp với một không dấu integer. Khi điều tra sâu hơn, tôi tìm thấy trong mã nguồn ...

các khối được đánh số liên tục, 0 đến 0xFFFFFFFE .

Nhấn mạnh đậm của tôi. Xác nhận tính toán đầu tiên:

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

Postgres sử dụng số nguyên đã ký và do đó ngắn một bit. Tuy nhiên, tôi không thể xác định liệu biểu diễn văn bản có bị dịch chuyển để phù hợp với số nguyên đã ký hay không. Cho đến khi ai đó có thể làm rõ điều này, tôi sẽ quay trở lại bigint, hoạt động trong mọi trường hợp.

Diễn viên

Không có diễn viên nào được đăng ký cho tidloại trong Postgres 9.3:

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

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

Bạn vẫn có thể sử dụng text. Có một văn bản đại diện cho mọi thứ trong Postgres :

Một ngoại lệ quan trọng khác là "phôi chuyển đổi I / O tự động", được thực hiện bằng các hàm I / O của loại dữ liệu để chuyển đổi sang hoặc từ văn bản hoặc các loại chuỗi khác, không được trình bày rõ ràng trong pg_cast.

Biểu diễn văn bản khớp với điểm của một điểm, bao gồm hai float8số, biểu diễn đó không mất.

Bạn có thể truy cập số đầu tiên của một điểm bằng chỉ số 0. Truyền tới bigint. Voilá.

Hiệu suất

Tôi đã chạy thử nghiệm nhanh trên một bảng có 30k hàng (tốt nhất là 5) trên một vài biểu thức thay thế xuất hiện trong đầu, bao gồm cả bản gốc của bạn:

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;

intthay vì bigintở đây, chủ yếu là không liên quan cho mục đích thử nghiệm. Tôi đã không lặp lại cho bigint.
Dàn diễn viên t_tidxây dựng trên loại hỗn hợp do người dùng định nghĩa, như @Jake đã nhận xét.
Ý chính của nó: Đúc có xu hướng nhanh hơn thao tác chuỗi. Biểu thức thông thường là đắt tiền. Giải pháp trên là ngắn nhất và nhanh nhất.


1
Cảm ơn Erwin, công cụ hữu ích. Từ đây, có vẻ như ctidlà 6 byte với 4 cho trang và 2 cho hàng. Tôi đã lo lắng về việc chuyển sang floatnhưng tôi đoán tôi không cần từ những gì bạn nói ở đây. Có vẻ như một loại hỗn hợp do người dùng định nghĩa là chậm hơn nhiều khi sử dụng point, bạn có thấy điều đó không?
Jack Douglas

@JackDoumund: Sau khi điều tra thêm, tôi đã quay trở lại bigint. Hãy xem xét cập nhật.
Erwin Brandstetter

1
@JackDoumund: Tôi thích ý tưởng của bạn về việc chuyển sang loại hỗn hợp. Nó sạch sẽ và hoạt động rất tốt - ngay cả khi dàn diễn viên pointquay lại int8vẫn nhanh hơn). Truyền tới các loại được xác định trước sẽ luôn nhanh hơn một chút. Tôi đã thêm nó vào thử nghiệm của tôi để so sánh. Tôi (page_number bigint, row_number integer)chắc chắn điều đó.
Erwin Brandstetter

1
2^40chỉ có 1TB, không phải 32TB 2^45, được chia 2^13cho 2^32, do đó toàn bộ 32 bit là cần thiết cho số trang.
Daniel Vérité

1
Cũng có thể đáng lưu ý là pg_freespacemap sử dụng bigintcho blkno
Jack Douglas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.