sql >> データベース >  >> RDS >> PostgreSQL

ランダムタプルを取得するためのTablesampleおよびその他の方法

    PostgreSQLのTABLESAMPLEは、ランダムなタプルを取得する他の従来の方法と比較して、さらにいくつかの利点をもたらします。

    TABLESAMPLE はSQLSELECT句であり、SYSTEMという2つのサンプリングメソッドを提供します。 およびBERNOULLITABLESAMPLEの助けを借りて テーブルからランダムな行を簡単に取得できます。 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を比較しました (SYSTEMBERNOULLI )およびtsm_system_rows 古いランダムメソッドを使用したモジュール。

    ここで、調査結果をすばやく確認できます:

    • ORDER BY random() 並べ替えのために遅い
    • random() <= 0.1 BERNOULLIに似ています メソッド
    • ランダムな値で列を追加するには初期作業が必要であり、パフォーマンスの問題が発生する可能性があります
    • SYSTEM 高速ですが、行数を制御するのは困難です
    • tsm_system_rows 高速で、行数を制御できます

    その結果、tsm_system_rows ランダムな行をいくつか選択する他の方法よりも優れています。

    しかし、本当の勝者は間違いなくTABLESAMPLEです 自体。その拡張性のおかげで、カスタム拡張機能(つまり、tsm_system_rowstsm_system_timeTABLESAMPLEを使用して開発できます メソッド関数。

    開発者向けメモ: カスタムサンプリングメソッドの記述方法の詳細については、PostgreSQLドキュメントの「テーブルサンプリングメソッドの記述」の章を参照してください。

    将来への注意: 次のTABLESAMPLEで、AXLEプロジェクトとtsm_system_time拡張機能について説明します。 ブログ投稿。


    1. MySQLがいくつかの外部キーを削除する

    2. SQL ServerのVarchar(max)に対してNOT NULLがNULL値を返すのはなぜですか?

    3. PSQLでユーザーを作成する方法

    4. 2つの異なるテーブルの列sqlite3に結合します