PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Таблица извадка и други методи за получаване на произволни кортежи

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 публикация в блога.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Спрете (продължителното) изпълнение на SQL заявка в PostgreSQL, когато сесия или заявки вече не съществуват?

  2. Как да възстановим PostgreSQL дъмп файл в Postgres бази данни?

  3. Полиморфизъм в таблиците на SQL база данни?

  4. [Видео] Сила на индексиране в PostgreSQL

  5. PostgreSQL 12:Внедряване на K-Nearest Neighbor Space Partitioned Generalized Search Tree Indexs