TABLESAMPLE на PostgreSQL носи още няколко предимства в сравнение с други традиционни начини за получаване на произволни кортежи.
TABLESAMPLE
е SQL SELECT клауза и предоставя два метода за вземане на проби, които са SYSTEM
и BERNOULLI
. С помощта на TABLESAMPLE
можем лесно да извлечем произволни редове от таблица. За повече информация относно TABLESAMPLE можете да проверите предишната публикация в блога .
В тази публикация в блога ще говорим за алтернативни начини за получаване на произволни редове. Как хората избираха произволни редове преди TABLESAMPLE
, какви са предимствата и недостатъците на другите методи и какво спечелихме с TABLESAMPLE
?
Има страхотни публикации в блога за избор на произволни редове. Можете да започнете да четете следните публикации в блога, за да разберете задълбочено тази тема.
Моите мисли за получаване на произволен ред
Получаване на произволни редове от таблица на база данни
random_agg()
Нека сравним традиционните начини за получаване на произволни редове от таблица с новите начини, предоставени от TABLESAMPLE.
Преди TABLESAMPLE
клауза, имаше 3 често използвани метода за произволен избор на редове от таблица.
1- Подреждане по random()
За целите на тестването трябва да създадем таблица и да поставим някои данни в нея.
Нека създадем ts_test таблица и да вмъкнем 1M реда в нея:
CREATE TABLE ts_test (
id SERIAL PRIMARY KEY,
title TEXT
);
INSERT INTO ts_test (title)
SELECT
'Record #' || i
FROM
generate_series(1, 1000000) i;
Имайки предвид следния SQL израз за избор на 10 произволни реда:
SELECT * FROM ts_test ORDER BY random() LIMIT 10;
Принуждава PostgreSQL да извърши пълно сканиране на таблицата и да подреди. Следователно този метод не е предпочитан за таблици с голям брой редове поради съображения за производителност.
Нека разгледаме EXPLAIN ANALYZE
изход на тази заявка по-горе:
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)
Като EXPLAIN ANALYZE
посочва, избирането на 10 от 1M реда отне почти 2 секунди. Заявката също използва изхода на random()
като ключ за сортиране за подреждане на резултатите. Сортирането изглежда е най-отнемащата време задача тук. Нека изпълним това със сценарий, използвайки TABLESAMPLE
.
В PostgreSQL 9.5, за получаване на точен брой редове на случаен принцип, можем да използваме метода за вземане на проби SYSTEM_ROWS. Предоставя се от tsm_system_rows
contrib модул, той ни позволява да посочим колко реда трябва да бъдат върнати чрез извадка. Обикновено само исканият процент може да бъде посочен с TABLESAMPLE SYSTEM
и BERNOULLI
методи.
Първо, трябва да създадем tsm_system_rows
разширение за използване на този метод, тъй като и двете TABLESAMPLE SYSTEM
и TABLESAMPLE BERNOULLI
методите не гарантират, че предоставеният процент ще доведе до очаквания брой редове. Моля, проверете предишния TABLESAMPLE p ost да си спомня защо работят така.
Нека започнем със създаване на tsm_system_rows
разширение:
random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION
Сега нека сравним „ORDER BY random()
” EXPLAIN ANALYZE
извеждане с EXPLAIN ANALYZE
изход на tsm_system_rows
заявка, която връща 10 произволни реда от 1M таблица с редове.
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)
Цялата заявка отне 0,159 ms. Този метод за вземане на проби е изключително бърз в сравнение с „ORDER BY random()
” метод, който отне 1956,9 ms.
2- Сравнете с random()
Следният SQL ни позволява да извличаме произволни редове с 10% вероятност
SELECT * FROM ts_test WHERE random() <= 0.1;
Този метод е по-бърз от подреждането на случаен принцип, тъй като не сортира избраните редове. Той ще върне приблизителен процент редове от таблицата точно като BERNOULLI
или SYSTEM
TABLESAMPLE
методи.
Нека проверим EXPLAIN ANALYZE
изход на random()
запитване по-горе:
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)
Заявката отне 367,5 ms. Много по-добре от ORDER BY random()
. Но е по-трудно да се контролира точният брой редове. Нека сравним „random()
” EXPLAIN ANALYZE
извеждане с EXPLAIN ANALYZE
изход на TABLESAMPLE BERNOULLI
заявка, която връща приблизително 10% от произволните редове от таблицата с 1M редове.
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)
Дадохме 10 като параметър на BERNOULLI
защото ни трябват 10% от всички записи.
Тук можем да видим, че BERNOULLI
Изпълнението на метода отне 239,289 ms. Тези два метода са доста сходни по начина, по който работят, BERNOULLI
е малко по-бързо, тъй като произволният избор се извършва на по-ниско ниво. Едно предимство от използването на BERNOULLI
в сравнение с WHERE random() <= 0.1
е REPEATABLE
клауза, която описахме в предишния TABLESAMPLE
пост.
3- Допълнителна колона с произволна стойност
Този метод предлага добавяне на нова колона с произволно присвоени стойности, добавяне на индекс към нея, извършване на сортиране по тази колона и по избор периодично актуализиране на стойностите им, за да се произволно рандомизира разпределението.
Тази стратегия позволява предимно повторяема произволна извадка. Той работи много по-бързо от първия метод, но изисква усилие за настройка за първи път и води до разходи за производителност при операции за вмъкване, актуализиране и изтриване.
Нека приложим този метод в нашия ts_test
таблица.
Първо, ще добавим нова колона, наречена randomcolumn
с типа с двойна точност, защото random()
на PostgreSQL функцията връща число с двойна точност.
random=# ALTER TABLE ts_test ADD COLUMN randomcolumn DOUBLE PRECISION;
ALTER TABLE
След това ще актуализираме новата колона с помощта на random()
функция.
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
Този метод има първоначална цена за създаване на нова колона и попълване на тази нова колона със произволни (0,0-1,0) стойности, но това е еднократна цена. В този пример, за 1M реда, това отне почти 8,5 секунди.
Нека се опитаме да наблюдаваме дали резултатите ни са възпроизводими, като запитаме 100 реда с нашия нов метод:
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)
Когато изпълним заявката по-горе, получаваме предимно същия набор от резултати, но това не е гарантирано, защото използвахме random()
функция за попълване на randomcolumn
стойности и в този случай повече от една колона може да имат една и съща стойност. За да сме сигурни, че получаваме едни и същи резултати при всяко изпълнение, трябва да подобрим нашата заявка, като добавим колоната ID към ORDER BY
клауза, по този начин гарантираме, че ORDER BY
клаузата определя уникален набор от редове, тъй като колоната с идентификатор има индекс на първичен ключ.
Сега нека изпълним подобрената заявка по-долу:
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)
Сега сме сигурни, че можем да получим възпроизводима произволна извадка, като използваме този метод.
Време е да видите EXPLAIN ANALYZE
резултати от нашата последна заявка:
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)
За сравнение на този метод с TABLESAMPLE
методи, нека изберем SYSTEM
метод с REPEATABLE
опция, тъй като този метод ни дава възпроизводими резултати.
TABLESAMPLE
SYSTEM
методът основно прави извадка на ниво блок/страница; той чете и връща произволни страници от диска. По този начин той осигурява произволност на ниво страница вместо на ниво кортеж. Ето защо е трудно да се контролира точно броя на извлечените редове. Ако няма избрани страници, може да не получим никакъв резултат. Извадката на ниво страница също е склонна към ефект на групиране.
TABLESAMPLE
СИНТАКСИС е същият за BERNOULLI
и SYSTEM
методи, ще посочим процента, както направихме в BERNOULLI
метод.
Бързо напомняне: СИНТАКСИС
SELECT select_expression
FROM table_name
TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ]
...
За да извлечем приблизително 100 реда, трябва да поискаме 100 * 100 / 1 000 000 =0,01% от редовете на таблицата. Така че нашият процент ще бъде 0,01.
Преди да започнем да правим заявки, нека си спомним как REPEATABLE
върши работа. По принцип ще изберем начален параметър и ще получим едни и същи резултати всеки път, когато използваме едно и също начало с предишната заявка. Ако предоставим различно начало, заявката ще доведе до съвсем различен набор от резултати.
Нека се опитаме да видим резултатите със заявка.
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)
Получаваме 136 реда, тъй като можете да прецените, че това число зависи от това колко реда се съхраняват в една страница.
Сега нека се опитаме да изпълним същата заявка със същия начален номер:
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)
Получаваме същия набор от резултати благодарение на REPEATABLE
опция. Сега можем да сравним TABLESAMPLE SYSTEM
метод с метод на произволна колона, като погледнете EXPLAIN ANALYZE
изход.
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
методът използва пробно сканиране и е изключително бърз. Единственият недостатък на този метод е, че не можем да гарантираме, че предоставеният процент ще доведе до очаквания брой редове.
Заключение
В тази публикация в блога сравнихме стандартния TABLESAMPLE
(SYSTEM
, BERNOULLI
) и tsm_system_rows
модул с по-старите произволни методи.
Тук можете бързо да прегледате констатациите:
ORDER BY random()
е бавен поради сортиранеrandom() <= 0.1
е подобен наBERNOULLI
метод- Добавянето на колона със произволна стойност изисква първоначална работа и може да доведе до проблеми с производителността
SYSTEM
е бърз, но е трудно да се контролира броят на редоветеtsm_system_rows
е бърз и може да контролира броя на редовете
В резултат на това tsm_system_rows
побеждава всеки друг метод за избор само на няколко произволни реда.
Но истинският победител определено е TABLESAMPLE
себе си. Благодарение на неговата разширяемост, персонализирани разширения (т.е. tsm_system_rows
, tsm_system_time
) може да бъде разработен с помощта на TABLESAMPLE
функции на метода.
Бележка за програмиста: Повече информация за това как да напишете персонализирани методи за вземане на проби можете да намерите в главата за писане на метод за извадка от таблица в документацията на PostgreSQL.
Забележка за бъдещето: Ще обсъдим проект AXLE и разширение tsm_system_time в следващия ни TABLESAMPLE
публикация в блога.