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

大きなテーブルから親ごとに最新の子を取得します-クエリが遅すぎます

    重要な点は、JOINである可能性が最も高いです。 およびGROUP max(created)を取得するためだけにすべてを 。この値は個別に取得してください。

    ここで必要なすべてのインデックスについて言及しました:report_rank.created および外部キー。あなたはそこで大丈夫です。 (「大丈夫」よりも興味がある場合は、読み続けてください !)

    LEFT JOIN report_site プレーンなJOINに強制されます WHEREによって 句。プレーンなJOINに置き換えました 。また、構文を大幅に簡略化しました。

    2015年7月更新 よりシンプルで高速なクエリとよりスマートな機能を備えています。

    複数行のソリューション

    report_rank.created ユニークではない すべてが必要です 最新の行。
    ウィンドウ関数の使用 rank() サブクエリで。

    SELECT r.id, r.keyword_id, r.site_id
         , r.rank, r.url, r.competition
         , r.source, r.country, r.created  -- same as "max"
    FROM  (
       SELECT *, rank() OVER (ORDER BY created DESC NULLS LAST) AS rnk
       FROM   report_rank r
       WHERE  EXISTS (
          SELECT *
          FROM   report_site    s
          JOIN   report_profile p ON p.site_id = s.id
          JOIN   crm_client     c ON c.id      = p.client_id
          JOIN   auth_user      u ON u.id      = c.user_id
          WHERE  s.id = r.site_id
          AND    u.is_active
          AND    c.is_deleted = FALSE
          )
       ) sub
    WHERE  rnk = 1;
    

    DESC NULLS LASTが選ばれる理由 ?

    1行のソリューション

    report_rank.createdの場合 ユニークです または、任意の1行に満足しています max(created)を使用 :

    SELECT id, keyword_id, site_id
         , rank, url, competition
         , source, country, created  -- same as "max"
    FROM   report_rank r
    WHERE  EXISTS (
        SELECT 1
        FROM   report_site    s
        JOIN   report_profile p ON p.site_id = s.id
        JOIN   crm_client     c ON c.id      = p.client_id
        JOIN   auth_user      u ON u.id      = c.user_id
        WHERE  s.id = r.site_id
        AND    u.is_active
        AND    c.is_deleted = FALSE
       )
    -- AND  r.created > f_report_rank_cap()
    ORDER  BY r.created DESC NULLS LAST
    LIMIT  1;
    

    それでも、もっと速いはずです。その他のオプション:

    動的に調整された部分インデックスによる究極の速度

    最後のクエリでコメントされた部分に気づいたかもしれません:

    AND  r.created > f_report_rank_cap()
    

    あなたは50ミオについて言及しました。行、それはたくさんです。これが物事をスピードアップする方法です:

    • 単純なIMMUTABLEを作成します 可能な限り若い間、対象の行よりも古いことが保証されているタイムスタンプを返す関数。
    • 部分インデックス を作成します 若い行のみ-この関数に基づく。
    • WHEREを使用する インデックス条件に一致するクエリの条件。
    • これらのオブジェクトを動的DDLを使用して最新の行に更新する別の関数を作成します。 (マイナス安全マージン 最新の行が削除/非アクティブ化された場合-それが発生する可能性がある場合)
    • この二次関数をオフタイムに呼び出し、cronジョブごとまたはオンデマンドで最小限の同時アクティビティを実行します。何度でも害を及ぼすことはできません。テーブルに短い排他ロックが必要なだけです。

    これが完全なデモです 。
    @erikcw、以下の手順に従って、コメント部分をアクティブ化する必要があります。

    CREATE TABLE report_rank(created timestamp);
    INSERT INTO report_rank VALUES ('2011-11-11 11:11'),(now());
    
    -- initial function
    CREATE OR REPLACE FUNCTION f_report_rank_cap()
      RETURNS timestamp LANGUAGE sql COST 1 IMMUTABLE AS
    $y$SELECT timestamp '-infinity'$y$;  -- or as high as you can safely bet.
    
    -- initial index; 1st run indexes whole tbl if starting with '-infinity'
    CREATE INDEX report_rank_recent_idx ON report_rank (created DESC NULLS LAST)
    WHERE  created > f_report_rank_cap();
    
    -- function to update function & reindex
    CREATE OR REPLACE FUNCTION f_report_rank_set_cap()
      RETURNS void AS
    $func$
    DECLARE
       _secure_margin CONSTANT interval := interval '1 day';  -- adjust to your case
       _cap timestamp;  -- exclude older rows than this from partial index
    BEGIN
       SELECT max(created) - _secure_margin
       FROM   report_rank
       WHERE  created > f_report_rank_cap() + _secure_margin
       /*  not needed for the demo; @erikcw needs to activate this
       AND    EXISTS (
         SELECT *
         FROM   report_site    s
         JOIN   report_profile p ON p.site_id = s.id
         JOIN   crm_client     c ON c.id      = p.client_id
         JOIN   auth_user      u ON u.id      = c.user_id
         WHERE  s.id = r.site_id
         AND    u.is_active
         AND    c.is_deleted = FALSE)
       */
       INTO   _cap;
    
       IF FOUND THEN
         -- recreate function
         EXECUTE format('
         CREATE OR REPLACE FUNCTION f_report_rank_cap()
           RETURNS timestamp LANGUAGE sql IMMUTABLE AS
         $y$SELECT %L::timestamp$y$', _cap);
    
         -- reindex
         REINDEX INDEX report_rank_recent_idx;
       END IF;
    END
    $func$  LANGUAGE plpgsql;
    
    COMMENT ON FUNCTION f_report_rank_set_cap()
    IS 'Dynamically recreate function f_report_rank_cap()
        and reindex partial index on report_rank.';
    

    電話:

    SELECT f_report_rank_set_cap();
    

    参照:

    SELECT f_report_rank_cap();
    

    AND r.created > f_report_rank_cap()句のコメントを解除します 上記のクエリで違いを観察します。インデックスがEXPLAIN ANALYZEで使用されることを確認します 。

    同時実行性とREINDEXに関するマニュアル



    1. SQLiteの日付から日数を引く

    2. リモートMySQLサーバーに接続できません

    3. POSTの生データをログに記録するPHPスクリプト

    4. MySQLはphpで同時に複数のIDを選択します