PostgreSQLのTABLESAMPLEは、ランダムなタプルを取得する他の従来の方法と比較して、さらにいくつかの利点をもたらします。
TABLESAMPLE
はSQLSELECT句であり、SYSTEM
という2つのサンプリングメソッドを提供します。 およびBERNOULLI
。 TABLESAMPLE
の助けを借りて テーブルからランダムな行を簡単に取得できます。 TABLESAMPLEの詳細については、以前のブログ投稿を確認してください。 。
このブログ投稿では、ランダムな行を取得する別の方法について説明します。 TABLESAMPLE
の前に人々がランダムな行を選択した方法 、他の方法の長所と短所、およびTABLESAMPLE
で得たもの ?
ランダムな行の選択に関するすばらしいブログ投稿があります。次のブログ投稿を読み始めて、このトピックを深く理解することができます。
ランダムな行を取得することについての私の考え
データベーステーブルからのランダム行の取得
random_agg()
テーブルからランダムな行を取得する従来の方法と、TABLESAMPLEが提供する新しい方法を比較してみましょう。
TABLESAMPLE
の前 条項では、テーブルから行をランダムに選択するために一般的に使用される3つの方法がありました。
1-ランダムで並べ替え()
テストの目的で、テーブルを作成し、その中にデータを配置する必要があります。
ts_testテーブルを作成し、それに100万行を挿入しましょう:
CREATE TABLE ts_test (
id SERIAL PRIMARY KEY,
title TEXT
);
INSERT INTO ts_test (title)
SELECT
'Record #' || i
FROM
generate_series(1, 1000000) i;
10個のランダムな行を選択するための次のSQLステートメントを検討します。
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
として 100万行から10行を選択するのに2秒近くかかったと指摘しています。クエリでは、random()
の出力も使用されました 結果を並べ替えるためのソートキーとして。ここでは、並べ替えが最も時間のかかる作業のようです。 TABLESAMPLE
を使用したシナリオでこれを実行してみましょう 。
PostgreSQL 9.5では、正確な行数をランダムに取得するために、SYSTEM_ROWSサンプリングメソッドを使用できます。 tsm_system_rows
によって提供されます contribモジュールを使用すると、サンプリングによって返される行数を指定できます。通常、TABLESAMPLE SYSTEM
で指定できるのは、要求されたパーセンテージのみです。 およびBERNOULLI
メソッド。
まず、tsm_system_rows
を作成する必要があります TABLESAMPLE SYSTEM
の両方から、このメソッドを使用するための拡張機能 およびTABLESAMPLE BERNOULLI
メソッドは、提供されたパーセンテージが予想される行数になることを保証しません。前のTABLESAMPLEpを確認してください なぜ彼らがそのように働くのかを思い出すのが一番です。
tsm_system_rows
を作成することから始めましょう 拡張子:
random=# CREATE EXTENSION tsm_system_rows;
CREATE EXTENSION
それでは、「ORDER BY random()
」を比較してみましょう。 ” EXPLAIN ANALYZE
EXPLAIN ANALYZE
で出力 tsm_system_rows
の出力 1M行テーブルからランダムな10行を返すクエリ。
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ミリ秒かかりました。このサンプリング方法は、「ORDER BY random()
」と比較して非常に高速です。 」1956.9ミリ秒かかった方法。
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ミリ秒かかりました。 ORDER BY random()
よりもはるかに優れています 。ただし、正確な行数を制御することは困難です。 「random()
」を比較してみましょう ” EXPLAIN ANALYZE
EXPLAIN ANALYZE
で出力 TABLESAMPLE BERNOULLI
の出力 1M行テーブルからランダム行の約10%を返すクエリ。
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)
BERNOULLI
のパラメータとして10を指定しました すべてのレコードの10%が必要だからです。
ここで、BERNOULLI
メソッドの実行には239.289ミリ秒かかりました。これらの2つの方法は、動作方法が非常に似ています。BERNOULLI
ランダム選択はすべて低レベルで行われるため、わずかに高速です。 BERNOULLI
を使用する利点の1つ WHERE random() <= 0.1
と比較 REPEATABLE
です 前のTABLESAMPLE
で説明した句 投稿。
3-ランダムな値を持つ追加の列
この方法では、ランダムに割り当てられた値を持つ新しい列を追加し、それにインデックスを追加し、その列による並べ替えを実行し、オプションで値を定期的に更新して分布をランダム化することをお勧めします。
この戦略により、ほとんど繰り返し可能なランダムサンプリングが可能になります。最初の方法よりもはるかに高速に動作しますが、初めてセットアップするのに手間がかかり、挿入、更新、および削除操作のパフォーマンスが低下します。
このメソッドをts_test
に適用してみましょう テーブル。
まず、randomcolumn
という新しい列を追加します PostgreSQLのrandom()
のため、倍精度型を使用します 関数は倍精度で数値を返します。
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)の値を入力するための初期費用がかかりますが、これは1回限りの費用です。この例では、100万行の場合、約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
id列には主キーインデックスがあるため、句は一意の行セットを指定します。
次に、以下の改善されたクエリを実行しましょう:
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
SYNTAXは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)
この数は1ページに格納されている行数に依存すると考えられるため、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ドキュメントの「テーブルサンプリングメソッドの記述」の章を参照してください。
将来への注意: 次のTABLESAMPLE
で、AXLEプロジェクトとtsm_system_time拡張機能について説明します。 ブログ投稿。