TABLESAMPLE PostgreSQL membawa beberapa keuntungan lebih banyak dibandingkan dengan cara tradisional lainnya untuk mendapatkan tupel acak.
TABLESAMPLE
adalah klausa SQL SELECT dan menyediakan dua metode pengambilan sampel yaitu SYSTEM
dan BERNOULLI
. Dengan bantuan TABLESAMPLE
kita dapat dengan mudah mengambil baris acak dari sebuah tabel. Untuk membaca lebih lanjut tentang TABLESAMPLE Anda dapat memeriksa posting blog sebelumnya .
Dalam posting blog ini kita akan berbicara tentang cara alternatif untuk mendapatkan baris acak. Bagaimana orang memilih baris acak sebelum TABLESAMPLE
, apa pro dan kontra dari metode lain dan apa yang kami peroleh dengan TABLESAMPLE
?
Ada postingan blog yang luar biasa tentang memilih baris acak, Anda dapat mulai membaca postingan blog berikut untuk mendapatkan pemahaman mendalam tentang topik ini.
Pikiran Saya Mendapatkan Baris Acak
Mendapatkan Baris Acak dari Tabel Database
random_agg()
Mari kita bandingkan cara tradisional untuk mendapatkan baris acak dari tabel dengan cara baru yang disediakan oleh TABLESAMPLE.
Sebelum TABLESAMPLE
klausa, ada 3 metode yang umum digunakan untuk memilih baris dari tabel secara acak.
1- Pesan secara acak()
Untuk tujuan pengujian, kita perlu membuat tabel dan memasukkan beberapa data ke dalamnya.
Mari buat tabel ts_test dan masukkan 1 juta baris ke dalamnya:
CREATE TABLE ts_test (
id SERIAL PRIMARY KEY,
title TEXT
);
INSERT INTO ts_test (title)
SELECT
'Record #' || i
FROM
generate_series(1, 1000000) i;
Mempertimbangkan pernyataan SQL berikut untuk memilih 10 baris acak:
SELECT * FROM ts_test ORDER BY random() LIMIT 10;
Menyebabkan PostgreSQL melakukan pemindaian tabel penuh dan juga pemesanan. Oleh karena itu metode ini tidak disukai untuk tabel dengan jumlah baris yang banyak karena alasan kinerja.
Mari kita lihat EXPLAIN ANALYZE
output dari kueri ini di atas:
random=# EXPLAIN ANALYZE SELECT * FROM ts_test ORDER BY random() LIMIT 10;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Limit (cost=33959.03..33959.05 rows=10 width=36) (actual time=1956.786..1956.807 rows=10 loops=1)
-> Sort (cost=33959.03..35981.18 rows=808863 width=36) (actual time=1956.780..1956.789 rows=10 loops=1)
Sort Key: (random())
Sort Method: top-N heapsort Memory: 25kB
-> Seq Scan on ts_test (cost=0.00..16479.79 rows=808863 width=36) (actual time=0.276..1001.109 rows=1000000 loops=1)
Planning time: 1.434 ms
Execution time: 1956.900 ms
(7 rows)
Sebagai EXPLAIN ANALYZE
menunjukkan, memilih 10 dari 1 juta baris membutuhkan waktu hampir 2 detik. Kueri juga menggunakan keluaran random()
sebagai kunci pengurutan untuk mengurutkan hasil. Menyortir tampaknya menjadi tugas yang paling memakan waktu di sini. Mari kita jalankan ini dengan skenario menggunakan TABLESAMPLE
.
Di PostgreSQL 9.5, untuk mendapatkan jumlah baris yang tepat secara acak, kita dapat menggunakan metode pengambilan sampel SYSTEM_ROWS. Disediakan oleh tsm_system_rows
modul contrib, memungkinkan kita untuk menentukan berapa banyak baris yang harus dikembalikan dengan pengambilan sampel. Biasanya hanya persentase yang diminta yang dapat ditentukan dengan TABLESAMPLE SYSTEM
dan BERNOULLI
metode.
Pertama, kita harus membuat tsm_system_rows
ekstensi untuk menggunakan metode ini karena keduanya TABLESAMPLE SYSTEM
dan TABLESAMPLE BERNOULLI
metode tidak menjamin bahwa persentase yang diberikan akan menghasilkan jumlah baris yang diharapkan. Silakan periksa TABLESAMPLE p sebelumnya harus mengingat mengapa mereka bekerja seperti itu.
Mari kita mulai dengan membuat tsm_system_rows
ekstensi:
random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION
Sekarang mari kita bandingkan “ORDER BY random()
” EXPLAIN ANALYZE
output dengan EXPLAIN ANALYZE
keluaran tsm_system_rows
kueri yang mengembalikan 10 baris acak dari 1 juta tabel baris.
random=# EXPLAIN ANALYZE SELECT * FROM ts_test TABLESAMPLE SYSTEM_ROWS(10);
QUERY PLAN
-------------------------------------------------------------------------------------------------------
Sample Scan on ts_test (cost=0.00..4.10 rows=10 width=18) (actual time=0.069..0.083 rows=10 loops=1)
Sampling: system_rows ('10'::bigint)
Planning time: 0.646 ms
Execution time: 0.159 ms
(4 rows)
Seluruh kueri membutuhkan waktu 0,159 ms. Metode pengambilan sampel ini sangat cepat dibandingkan dengan metode “ORDER BY random()
” yang membutuhkan waktu 1956,9 md.
2- Bandingkan dengan random()
SQL berikut memungkinkan kita untuk mengambil baris acak dengan probabilitas 10%
SELECT * FROM ts_test WHERE random() <= 0.1;
Metode ini lebih cepat daripada memesan secara acak karena tidak mengurutkan baris yang dipilih. Ini akan mengembalikan perkiraan persentase baris dari tabel seperti BERNOULLI
atau SYSTEM
TABLESAMPLE
metode.
Mari kita periksa EXPLAIN ANALYZE
keluaran random()
kueri di atas:
random=# EXPLAIN ANALYZE SELECT * FROM ts_test WHERE random() <= 0.1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------
Seq Scan on ts_test (cost=0.00..21369.00 rows=333333 width=18) (actual time=0.089..280.483 rows=100014 loops=1)
Filter: (random() <= '0.1'::double precision)
Rows Removed by Filter: 899986
Planning time: 0.704 ms
Execution time: 367.527 ms
(5 rows)
Kueri membutuhkan waktu 367,5 md. Jauh lebih baik daripada ORDER BY random()
. Tetapi lebih sulit untuk mengontrol jumlah baris yang tepat. Mari kita bandingkan “random()
” EXPLAIN ANALYZE
output dengan EXPLAIN ANALYZE
keluaran TABLESAMPLE BERNOULLI
kueri yang mengembalikan sekitar 10% baris acak dari tabel 1 juta baris.
random=# EXPLAIN ANALYZE SELECT * FROM ts_test TABLESAMPLE BERNOULLI (10);
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Sample Scan on ts_test (cost=0.00..7369.00 rows=100000 width=18) (actual time=0.015..147.002 rows=100155 loops=1)
Sampling: bernoulli ('10'::real)
Planning time: 0.076 ms
Execution time: 239.289 ms
(4 rows)
Kami memberikan 10 sebagai parameter untuk BERNOULLI
karena kami membutuhkan 10% dari semua catatan.
Di sini kita dapat melihat bahwa BERNOULLI
metode ini membutuhkan 239.289 ms untuk dieksekusi. Kedua metode ini sangat mirip dalam cara kerjanya, BERNOULLI
sedikit lebih cepat karena pemilihan acak semua dilakukan pada level yang lebih rendah. Salah satu keuntungan menggunakan BERNOULLI
dibandingkan dengan WHERE random() <= 0.1
adalah REPEATABLE
klausa yang kami jelaskan di TABLESAMPLE
sebelumnya pos.
3- Kolom tambahan dengan nilai acak
Metode ini menyarankan untuk menambahkan kolom baru dengan nilai yang ditetapkan secara acak, menambahkan indeks ke dalamnya, melakukan pengurutan berdasarkan kolom tersebut, dan secara opsional memperbarui nilainya secara berkala untuk mengacak distribusi.
Strategi ini memungkinkan pengambilan sampel acak yang sebagian besar dapat diulang. Ini bekerja jauh lebih cepat daripada metode pertama, tetapi perlu upaya untuk menyiapkan untuk pertama kalinya, dan menghasilkan biaya kinerja dalam operasi penyisipan, pembaruan, dan penghapusan.
Mari kita terapkan metode ini pada ts_test
kita tabel.
Pertama, kita akan menambahkan kolom baru bernama randomcolumn
dengan tipe presisi ganda karena PostgreSQL random()
fungsi mengembalikan angka dalam presisi ganda.
random=# ALTER TABLE ts_test ADD COLUMN randomcolumn DOUBLE PRECISION;
ALTER TABLE
Kemudian kami akan memperbarui kolom baru menggunakan random()
fungsi.
random=# \timing
Timing is on.
random=# BEGIN;
BEGIN
Time: 2.071 ms
random=# UPDATE ts_test SET randomcolumn = RANDOM();
UPDATE 1000000
Time: 8483.741 ms
random=# COMMIT;
COMMIT
Time: 2.615 ms
Metode ini memiliki biaya awal untuk membuat kolom baru dan mengisi kolom baru tersebut dengan nilai acak (0,0-1,0), tetapi ini adalah biaya satu kali. Dalam contoh ini, untuk 1 juta baris, dibutuhkan waktu hampir 8,5 detik.
Mari kita coba amati apakah hasil kita dapat direproduksi dengan membuat kueri 100 baris dengan metode baru kita:
random=# SELECT * FROM ts_test ORDER BY randomcolumn LIMIT 100;
-------+---------------+----------------------
13522 | Record #13522 | 6.4261257648468e-08
671584 | Record #671584 | 6.4261257648468e-07
714012 | Record #714012 | 1.95764005184174e-06
162016 | Record #162016 | 3.44449654221535e-06
106867 | Record #106867 | 3.66196036338806e-06
865669 | Record #865669 | 3.96883115172386e-06
927 | Record #927 | 4.65428456664085e-06
526017 | Record #526017 | 4.65987250208855e-06
98338 | Record #98338 | 4.91179525852203e-06
769625 | Record #769625 | 4.91319224238396e-06
...
462484 | Record #462484 | 9.83504578471184e-05
(100 rows)
Saat kami menjalankan kueri di atas, sebagian besar kami mendapatkan kumpulan hasil yang sama tetapi ini tidak dijamin karena kami menggunakan random()
fungsi untuk mengisi randomcolumn
nilai dan dalam hal ini lebih dari satu kolom mungkin memiliki nilai yang sama. Untuk memastikan bahwa kami mendapatkan hasil yang sama untuk setiap kali dijalankan, kami harus meningkatkan kueri kami dengan menambahkan kolom ID ke ORDER BY
klausa, dengan cara ini kami memastikan bahwa ORDER BY
klausa menentukan kumpulan baris yang unik, karena kolom id memiliki indeks kunci utama di atasnya.
Sekarang mari kita jalankan kueri yang ditingkatkan di bawah ini:
random=# SELECT * FROM ts_test ORDER BY randomcolumn, id LIMIT 100;
id | title | randomcolumn
--------+----------------+----------------------
13522 | Record #13522 | 6.4261257648468e-08
671584 | Record #671584 | 6.4261257648468e-07
714012 | Record #714012 | 1.95764005184174e-06
162016 | Record #162016 | 3.44449654221535e-06
106867 | Record #106867 | 3.66196036338806e-06
865669 | Record #865669 | 3.96883115172386e-06
927 | Record #927 | 4.65428456664085e-06
526017 | Record #526017 | 4.65987250208855e-06
98338 | Record #98338 | 4.91179525852203e-06
769625 | Record #769625 | 4.91319224238396e-06
...
462484 | Record #462484 | 9.83504578471184e-05
(100 rows)
Sekarang kami yakin bahwa kami bisa mendapatkan sampel acak yang dapat direproduksi dengan menggunakan metode ini.
Saatnya melihat EXPLAIN ANALYZE
hasil dari kueri terakhir kami:
random=# EXPLAIN ANALYZE SELECT * FROM ts_test ORDER BY randomcolumn, id LIMIT 100;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Limit (cost=55571.28..55571.53 rows=100 width=26) (actual time=1951.811..1952.039 rows=100 loops=1)
-> Sort (cost=55571.28..58071.28 rows=1000000 width=26) (actual time=1951.804..1951.891 rows=100 loops=1)
Sort Key: randomcolumn, id
Sort Method: top-N heapsort Memory: 32kB
-> Seq Scan on ts_test (cost=0.00..17352.00 rows=1000000 width=26) (actual time=0.058..995.011 rows=1000000 loops=1)
Planning time: 0.481 ms
Execution time: 1952.215 ms
(7 rows)
Untuk membandingkan metode ini dengan TABLESAMPLE
metode, mari kita pilih SYSTEM
metode dengan REPEATABLE
pilihan, karena metode ini memberi kita hasil yang dapat direproduksi.
TABLESAMPLE
SYSTEM
metode pada dasarnya melakukan pengambilan sampel tingkat blok/halaman; ia membaca dan mengembalikan halaman acak dari disk. Dengan demikian ia memberikan keacakan di tingkat halaman alih-alih tingkat Tuple. Itulah mengapa sulit untuk mengontrol jumlah baris yang diambil dengan tepat. Jika tidak ada halaman yang diambil, kami mungkin tidak mendapatkan hasil apa pun. Pengambilan sampel tingkat halaman juga rentan terhadap efek pengelompokan.
TABLESAMPLE
SYNTAX sama untuk BERNOULLI
dan SYSTEM
metode, kami akan menentukan persentase seperti yang kami lakukan di BERNOULLI
metode.
Pengingat Cepat: SYNTAX
SELECT select_expression
FROM table_name
TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ]
...
Untuk mengambil sekitar 100 baris, kita perlu meminta 100 * 100 / 1 000 000 =0,01% dari baris tabel. Jadi persentase kita adalah 0,01.
Sebelum memulai query, mari kita ingat bagaimana REPEATABLE
bekerja. Pada dasarnya kita akan memilih parameter seed dan kita akan mendapatkan hasil yang sama untuk setiap kali kita menggunakan seed yang sama dengan kueri sebelumnya. Jika kami menyediakan benih yang berbeda, kueri akan menghasilkan kumpulan hasil yang sangat berbeda.
Mari kita coba melihat hasilnya dengan kueri.
random=# Select * from ts_test tablesample system (0.01) repeatable (60);
id | title | randomcolumn
--------+----------------+---------------------
659598 | Record #659598 | 0.964113113470376
659599 | Record #659599 | 0.531714483164251
659600 | Record #659600 | 0.477636905387044
659601 | Record #659601 | 0.861940925940871
659602 | Record #659602 | 0.545977566856891
659603 | Record #659603 | 0.326795583125204
659604 | Record #659604 | 0.469275736715645
659605 | Record #659605 | 0.734953186474741
659606 | Record #659606 | 0.41613544523716
...
659732 | Record #659732 | 0.893704727292061
659733 | Record #659733 | 0.847225237637758
(136 rows)
Kami mendapatkan 136 baris, karena Anda dapat menganggap jumlah ini tergantung pada berapa banyak baris yang disimpan dalam satu halaman.
Sekarang mari kita coba menjalankan kueri yang sama dengan nomor benih yang sama:
random=# Select * from ts_test tablesample system (0.01) repeatable (60);
id | title | randomcolumn
--------+----------------+---------------------
659598 | Record #659598 | 0.964113113470376
659599 | Record #659599 | 0.531714483164251
659600 | Record #659600 | 0.477636905387044
659601 | Record #659601 | 0.861940925940871
659602 | Record #659602 | 0.545977566856891
659603 | Record #659603 | 0.326795583125204
659604 | Record #659604 | 0.469275736715645
659605 | Record #659605 | 0.734953186474741
659606 | Record #659606 | 0.41613544523716
...
659732 | Record #659732 | 0.893704727292061
659733 | Record #659733 | 0.847225237637758
(136 rows)
Kami mendapatkan hasil yang sama berkat REPEATABLE
pilihan. Sekarang kita dapat membandingkan TABLESAMPLE SYSTEM
metode dengan metode kolom acak dengan melihat EXPLAIN ANALYZE
keluaran.
random=# EXPLAIN ANALYZE SELECT * FROM ts_test TABLESAMPLE SYSTEM (0.01) REPEATABLE (60);
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Sample Scan on ts_test (cost=0.00..5.00 rows=100 width=26) (actual time=0.091..0.230 rows=136 loops=1)
Sampling: system ('0.01'::real) REPEATABLE ('60'::double precision)
Planning time: 0.323 ms
Execution time: 0.398 ms
(4 rows)
SYSTEM
metode ini menggunakan pemindaian sampel dan sangat cepat. Satu-satunya kemunduran dari metode ini adalah kami tidak dapat menjamin bahwa persentase yang diberikan akan menghasilkan jumlah baris yang diharapkan.
Kesimpulan
Dalam posting blog ini kami membandingkan TABLESAMPLE
standar (SYSTEM
, BERNOULLI
) dan tsm_system_rows
modul dengan metode acak yang lebih lama.
Di sini Anda dapat meninjau temuan dengan cepat:
ORDER BY random()
lambat karena menyortirrandom() <= 0.1
mirip denganBERNOULLI
metode- Menambahkan kolom dengan nilai acak membutuhkan pekerjaan awal dan dapat menyebabkan masalah kinerja
SYSTEM
cepat tetapi sulit untuk mengontrol jumlah baristsm_system_rows
cepat dan dapat mengontrol jumlah baris
Akibatnya tsm_system_rows
mengalahkan metode lain untuk memilih hanya beberapa baris acak.
Tapi pemenang sebenarnya adalah TABLESAMPLE
diri. Berkat ekstensibilitasnya, ekstensi khusus (yaitu tsm_system_rows
, tsm_system_time
) dapat dikembangkan menggunakan TABLESAMPLE
fungsi metode.
Catatan pengembang: Informasi lebih lanjut tentang cara menulis metode pengambilan sampel khusus dapat ditemukan di bab Penulisan Tabel Metode Pengambilan Sampel di dokumentasi PostgreSQL.
Catatan untuk masa depan: Kami akan membahas Proyek AXLE dan ekstensi tsm_system_time di TABLESAMPLE
berikutnya entri blog.