Bagaimana cara saya menguraikan ctid menjadi nomor halaman dan baris?

16

Setiap baris dalam tabel memiliki kolom ctid tipe sistemtid yang mewakili lokasi fisik baris:

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

Aku di sini

Apa cara terbaik untuk mendapatkan nomor halaman dari jenis ctidyang paling sesuai (mis. integer, bigintAtau numeric(1000,0))?

Satu- satunya cara yang bisa saya pikirkan adalah sangat jelek.

Jack Douglas
sumber
1
IIRC itu adalah tipe vektor dan kami tidak memiliki metode pengaksesor ini. Saya tidak yakin apakah Anda dapat melakukannya dari fungsi C. Craig akan mengatakan dengan pasti :)
dezso
2
Bisakah Anda berperan sebagai POINT? Misalnya. select ct[0], ct[1] from (select ctid::text::point as ct from pg_class where ...) y;
bma
1
Judul menyarankan Anda setelah nomor halaman dan indeks tuple , kemudian Anda mempersempit ke nomor halaman. Aku pergi dengan versi dalam tubuh, indeks tuple adalah ekstensi sepele.
Erwin Brandstetter

Jawaban:

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

Biola Anda dengan solusi saya.

@ bma sudah mengisyaratkan sesuatu yang mirip dalam komentar. Ini adalah ...

Dasar pemikiran untuk tipenya

ctidadalah tipe tid(tuple identifier), yang disebut ItemPointerdalam kode C. Per dokumentasi:

Ini adalah tipe data dari kolom sistem ctid. ID tuple adalah pasangan ( nomor blok , indeks tuple dalam blok ) yang mengidentifikasi lokasi fisik baris dalam tabelnya.

Penekanan berani saya. Dan:

( ItemPointer, juga dikenal sebagai CTID)

Satu blok adalah 8 KB dalam instalasi standar. Ukuran Tabel Maksimal adalah 32 TB . Secara logis berikut bahwa nomor blok harus mengakomodasi setidaknya maksimum (perhitungan ditetapkan sesuai dengan komentar oleh @Daniel):

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

Yang cocok dengan yang tidak ditandatangani integer. Pada penyelidikan lebih lanjut saya menemukan dalam kode sumber yang ...

blok diberi nomor secara berurutan, 0 hingga 0xFFFFFFFE .

Penekanan berani saya. Yang mengkonfirmasi perhitungan pertama:

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

Postgres menggunakan integer bertanda tangan dan karenanya sedikit pendek. Saya tidak bisa menjelaskan, apakah representasi teks digeser untuk mengakomodasi integer yang ditandatangani. Sampai seseorang dapat membereskan ini, saya akan kembali ke bigint, yang bekerja dalam hal apa pun.

Pemeran

Tidak ada pemeran terdaftar untuk tidjenis di Postgres 9.3:

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

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

Anda masih bisa memilih text. Ada representasi teks untuk semua yang ada di Postgres :

Pengecualian penting lainnya adalah bahwa "gips konversi I / O otomatis", yang dilakukan dengan menggunakan fungsi I / O tipe data untuk mengonversi ke atau dari teks atau tipe string lainnya, tidak diwakili secara eksplisit dalam pg_cast.

Representasi teks cocok dengan titik, yang terdiri dari dua float8angka, yang dilemparkan adalah lossless.

Anda dapat mengakses angka pertama dari suatu titik dengan indeks 0. Cast to bigint. Voila.

Performa

Saya menjalankan tes cepat di atas meja dengan baris 30rb (terbaik 5) pada beberapa ekspresi alternatif yang muncul di pikiran, termasuk yang asli:

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;

intalih-alih di bigintsini, sebagian besar tidak relevan untuk tujuan tes. Saya tidak mengulanginya bigint.
Para pemain t_tidmembangun berdasarkan tipe komposit yang ditentukan pengguna, seperti @Jake berkomentar.
Inti dari itu: Casting cenderung lebih cepat daripada manipulasi string. Ekspresi reguler mahal. Solusi di atas adalah yang terpendek dan tercepat.

Erwin Brandstetter
sumber
1
Terima kasih Erwin, hal-hal yang bermanfaat. Dari sini sepertinya ctidadalah 6 byte dengan 4 untuk halaman dan 2 untuk baris. Saya khawatir tentang casting untuk floattetapi saya kira saya tidak perlu dari apa yang Anda katakan di sini. Sepertinya tipe komposit yang ditentukan pengguna jauh lebih lambat daripada menggunakan point, apakah Anda juga menemukannya?
Jack Douglas
@JackDouglas: Setelah diselidiki lebih lanjut, saya kembali ke bigint. Pertimbangkan pembaruan.
Erwin Brandstetter
1
@JackDouglas: Saya suka ide Anda tentang pemeran untuk tipe komposit. Ini bersih dan berkinerja sangat baik - bahkan jika para pemain ke pointdan kembali ke int8masih lebih cepat). Cast ke tipe yang sudah ditentukan akan selalu sedikit lebih cepat. Saya menambahkannya ke tes saya untuk membandingkan. Saya akan (page_number bigint, row_number integer)memastikannya.
Erwin Brandstetter
1
2^40hanya 1TB, bukan 32TB yang 2^45, yang dibagi dengan 2^13memberi 2^32, maka 32 bit penuh diperlukan untuk nomor halaman.
Daniel Vérité
1
Mungkin juga patut dicatat adalah bahwa pg_freespacemap menggunakan bigintuntuk blkno
Jack Douglas