Apa yang diambil dari disk saat query?

13

Pertanyaan yang cukup sederhana, mungkin dijawab di suatu tempat, tetapi sepertinya saya tidak dapat membentuk pertanyaan pencarian yang tepat untuk Google ...

Apakah jumlah kolom dalam tabel tertentu memengaruhi kinerja kueri, saat kueri pada subset tabel itu?

Misalnya, jika tabel Foo memiliki 20 kolom, tetapi kueri saya hanya memilih 5 kolom tersebut, apakah memiliki 20 (dibandingkan, katakanlah, 10) kolom mempengaruhi kinerja kueri? Asumsikan untuk kesederhanaan bahwa apa pun dalam klausa WHERE termasuk dalam 5 kolom tersebut.

Saya khawatir tentang penggunaan cache buffer Postgres di samping cache disk sistem operasi. Saya sangat kehilangan pemahaman tentang desain penyimpanan fisik Postgres. Tabel disimpan di beberapa halaman (ukurannya 8k per halaman), tapi saya tidak begitu mengerti bagaimana tuple diatur dari sana. Apakah PG cukup pintar untuk hanya mengambil dari disk data yang terdiri dari 5 kolom itu?

Jmoney38
sumber
Anda sedang berbicara tentang mengambil 50 byte tetapi bukan yang tersisa 150. Disk Anda mungkin membaca secara bertahap lebih besar dari itu!
Andomar
Dari mana Anda mendapatkan angka-angka itu?
Jmoney38

Jawaban:

14

Penyimpanan fisik untuk baris dijelaskan dalam dokumen di Layout Halaman Database . Konten kolom untuk baris yang sama semuanya disimpan di halaman disk yang sama, dengan pengecualian khusus konten TOAST (terlalu besar untuk muat dalam satu halaman). Konten diekstraksi secara berurutan dalam setiap baris, seperti yang dijelaskan:

Untuk membaca data, Anda perlu memeriksa setiap atribut secara bergantian. Pertama periksa apakah bidang tersebut NULL sesuai dengan bitmap nol. Jika ya, pergi ke yang berikutnya. Kemudian pastikan Anda memiliki perataan yang benar. Jika bidang adalah bidang lebar tetap, maka semua byte hanya ditempatkan.

Dalam kasus yang paling sederhana (tanpa kolom TOAST'ed), postgres akan mengambil seluruh baris bahkan jika beberapa kolom diperlukan. Jadi dalam hal ini, jawabannya adalah ya, memiliki lebih banyak kolom mungkin memiliki dampak buruk yang jelas pada cache buffer pembuang, terutama jika konten kolom besar sementara masih di bawah ambang batas TOAST.

Sekarang kasus TOAST: ketika bidang individual melebihi ~ 2kB, mesin menyimpan konten bidang ke dalam tabel fisik yang terpisah. Itu juga ikut berperan ketika seluruh baris tidak masuk ke dalam sebuah halaman (8kB secara default): beberapa bidang dipindahkan ke penyimpanan TOAST. Doc mengatakan:

Jika itu adalah bidang panjang variabel (attlen = -1) maka itu sedikit lebih rumit. Semua tipe data panjang variabel berbagi struktur header umum struct varlena, yang mencakup panjang total nilai yang disimpan dan beberapa bit bendera. Bergantung pada flag, data dapat berupa inline atau dalam tabel TOAST; mungkin dikompresi juga

Konten TOAST tidak diambil ketika tidak diperlukan secara eksplisit, sehingga pengaruhnya terhadap jumlah total halaman yang akan diambil kecil (beberapa byte per kolom). Ini menjelaskan hasil dalam jawaban @ dezso.

Sedangkan untuk menulis, setiap baris dengan semua kolomnya sepenuhnya ditulis ulang pada setiap UPDATE, tidak peduli kolom apa yang diubah. Jadi memiliki lebih banyak kolom jelas lebih mahal untuk menulis.

Daniel Vérité
sumber
Itu adalah jawaban yang tepat. Persis apa yang saya cari. Terima kasih.
Jmoney38
1
Sumber yang bagus yang saya temukan sehubungan dengan struktur baris (pageinspect, dan beberapa penggunaan sampel) di sini .
Jmoney38
9

Jawaban Daniel berfokus pada biaya membaca setiap baris. Dalam konteks ini: Menempatkan NOT NULLkolom ukuran tetap terlebih dahulu di tabel Anda sedikit membantu. Menempatkan kolom yang relevan terlebih dahulu (yang Anda cari) sedikit membantu. Meminimalkan bantalan (karena penyelarasan data) dengan memainkan tetris penjajaran dengan kolom Anda dapat sedikit membantu. Tetapi efek yang paling penting belum disebutkan, terutama untuk tabel besar.

Kolom tambahan jelas membuat baris menutupi lebih banyak ruang disk, sehingga baris yang lebih sedikit muat pada satu halaman data (8 kB secara default). Baris individual tersebar di lebih banyak halaman. Mesin basis data umumnya harus mengambil seluruh halaman, bukan baris individual . Tidak masalah apakah baris individu agak lebih kecil atau lebih besar - selama jumlah halaman yang sama harus dibaca.

Jika kueri mengambil sebagian kecil tabel besar, di mana baris tersebar kurang lebih secara acak di seluruh tabel, didukung oleh indeks, ini akan menghasilkan jumlah halaman yang dibaca kurang lebih sama, dengan sedikit perhatian ke ukuran baris. Kolom yang tidak relevan tidak akan banyak memperlambat Anda dalam kasus (jarang) seperti itu.

Biasanya, Anda akan mengambil tambalan atau kelompok baris yang telah dimasukkan secara berurutan atau kedekatan dan berbagi halaman data. Baris-baris itu tersebar karena kekacauan, lebih banyak halaman disk harus dibaca untuk memenuhi permintaan Anda. Harus membaca lebih banyak halaman biasanya merupakan alasan paling penting agar permintaan menjadi lebih lambat. Dan itu adalah faktor paling penting mengapa kolom yang tidak relevan membuat permintaan Anda lebih lambat.

Dengan basis data besar, biasanya tidak ada cukup RAM untuk menyimpan semuanya dalam memori cache. Baris yang lebih besar menempati lebih banyak cache, lebih banyak pertikaian, hit cache lebih sedikit, lebih banyak I / O disk. Dan membaca disk biasanya jauh lebih mahal. Kurang begitu dengan SSD, tetapi perbedaan besar tetap. Ini menambah poin di atas tentang membaca halaman.

Ini mungkin atau mungkin tidak masalah jika kolom tidak relevan yang TOAST-ed. Kolom yang relevan mungkin juga TOAST-ed, membawa kembali banyak efek yang sama.

Erwin Brandstetter
sumber
1

Tes kecil:

CREATE TABLE test2 (
    id serial PRIMARY KEY,
    num integer,
    short_text varchar(32),
    longer_text varchar(1000),
    long_long_text text
);

INSERT INTO test2 (num, short_text, longer_text, long_long_text)
SELECT i, lpad('', 32, 'abcdefeghji'), lpad('', 1000, 'abcdefeghji'), lpad('', (random() * 10000)::integer, 'abcdefeghji')
FROM generate_series(1, 10000) a(i);

ANALYZE test2;

SELECT * FROM test2;
[...]
Time: 1091.331 ms

SELECT num FROM test2;
[...]
Time: 21.310 ms

Membatasi kueri ke 250 baris pertama ( WHERE num <= 250) menghasilkan masing-masing 34,539 ms dan 8,343 ms. Memilih semua kecuali long_long_textdari himpunan terbatas ini menghasilkan 18,432 ms. Ini menunjukkan bahwa dalam istilah Anda, PG cukup cerdas.

dezso
sumber
Yah, saya tentu menghargai masukannya. Namun, saya tidak bisa mengatakan dengan pasti bahwa skenario pengujian ini membuktikan apa yang awalnya saya usulkan. Ada beberapa masalah. Pertama, ketika Anda pertama kali menjalankan "SELECT * FROM test2", yang seharusnya telah mengisi cache buffer bersama Anda. Permintaan itu akan membutuhkan waktu lebih lama untuk mengambil dari disk. Dengan demikian, permintaan ke-2 secara teoritis akan jauh lebih cepat karena itu akan diambil dari cache SB. Tapi saya setuju bahwa itu 'menyarankan' bahwa PG hanya mengambil baris yang dibutuhkan, berdasarkan tes / perbandingan nanti.
Jmoney38
Anda benar, tes ini (sederhana) memiliki kekurangannya. Jika saya punya cukup waktu, saya akan mencoba untuk membahasnya juga.
dezso