【问题标题】:Query Optimization查询优化
【发布时间】:2026-02-14 06:30:01
【问题描述】:

我已经在 2 个具有完全相同结构的数据库中测试了以下查询,在第一个具有 4M 条目的数据库中,它在 33 秒内返回了结果。第二个表有 2900 万行,我执行查询已经 16 个小时了,我还没有得到回报。

SELECT sbvpip*4 as smallbvpip,btnvpip*4 as buttonvpip, sum(amt_won)*400/count(*) AS winrate, count(*) as count

FROM holdem_hand_player_statistics

    JOIN (

    SELECT id_player AS pid2, id_hand AS hid, sbvpip
    FROM holdem_hand_player_statistics

        JOIN (
        SELECT id_player AS pid, ROUND(avg(flg_vpip::int)*25) AS sbvpip
        FROM holdem_hand_player_statistics
        WHERE position = 8 AND cnt_players = 6
        GROUP BY id_player
        ) AS auxtable
        ON pid = id_player

    WHERE position = 8 AND cnt_players = 6
    ) AS auxtable2
    ON hid = id_hand


    JOIN (

    SELECT id_player AS pid4, id_hand AS hid2, btnvpip
    FROM holdem_hand_player_statistics

        JOIN (
        SELECT id_player AS pid3, ROUND(avg(flg_vpip::int)*25) AS btnvpip
        FROM holdem_hand_player_statistics
        WHERE position = 0 AND cnt_players = 6
        GROUP BY id_player
        ) AS auxtable3
        ON pid3 = id_player

    WHERE position = 0 AND cnt_players = 6
    ) AS auxtable4
    ON hid2 = id_hand


WHERE POSITION = 0 and cnt_players = 6



GROUP BY sbvpip,btnvpip
ORDER BY 1,2;

我能做些什么来让这个查询执行得更快?

表是否可能已损坏或类似情况?一张表只比另一张大 7~8 倍,但处理时间要多 15000 倍,这正常吗?

欢迎任何其他cmets!

如果我的英语不清楚,请告诉我,我会尝试用不同的方式表达自己。

非常感谢您的帮助,

附加信息:

从我使用的变量来看,其中 3 个是索引:id_hand、id_player、position。主键是 (id_hand, id_player)。该表共有 129 列和 6 个索引。

我还在两个表中都运行了 EXPLAIN,得到了不同的结果。这俩 结果在 gdocs 电子表格上: https://spreadsheets.google.com/ccc?key=tGxqxVNzHYznb1VVjtKyAuw&authkey=CJ-BiYkN&authkey=CJ-BiYkN#gid=0

【问题讨论】:

  • 对您的数据模型和索引一无所知,几乎不可能为您提供帮助。您能否也向我们展示 EXPLAIN 的结果?
  • 弗兰克,当我回到家时,我会得到这个信息并发布它。谢谢。
  • 你在那张桌子上做定期吸尘吗?
  • 我前几天抽空了。
  • 如果有很多删除/更新,那么“几天前”是不够的。我希望你没有禁用自动真空!

标签: sql optimization postgresql query-optimization


【解决方案1】:

我建议索引在其中一台服务器上不存在或不正确。

也可能有阻塞阻止查询完成。特别是如果有一个未提交的交易坐在那里。

【讨论】:

  • 这两个表是由同一个应用程序创建的,所以我希望它们具有完全相同的索引。使用此列的其他查询工作正常。但万一索引出现问题,我该如何检查呢?
  • 我不确定您将如何在 PostgreSQl 中查找索引。
【解决方案2】:

您可能会为更多的行使用更多的排序内存:您的work_mem 设置是什么?与 buffercache 类似 - 因为您要多次扫描同一个表,所以将行放入缓存中可能是至关重要的。

此外,您应该重新检查该查询并尝试找到不必多次将统计信息表连接回自身的方法。如果没有至少一些小的测试数据和预期的输出,很难给出建议。您使用的是哪个版本的 PostgreSQL?使用 8.4,您至少可以从单个 CTE 获得 auxtable 和 auxtable3...

【讨论】:

  • Araqnid,我到家时会检查一下这个设置。我还将检查我的 PostgreSQL 版本,但我相信它是 8.4,所以我将尝试使用单个 CTE 构建查询。小测试数据和预期输出是什么意思?我可以尝试得到它。谢谢。
【解决方案3】:

查询看起来不错。为了提高性能,请尝试像@HLGEM 所说的那样进行索引。 还可以尝试执行每个单独的子查询,看看哪个子查询的性能低。

【讨论】:

    【解决方案4】:

    我很容易相信查询需要更长的时间。您有一个 29M 行表,您正在对它执行多个组并在不同列上多次链接回自身。如果整个表不适合内存,则可能涉及到 1/7 行不需要的大量分页。向内工作,你是:

    1. 从位置 = 0 和 cnt_players = 6 的 29M 行表中选择
    2. 两次链接回 id_hand 列上的 29M 行表
    3. 针对cnt_players = 6 和位置0 和8 对29M 行表进行两次过滤,并按玩家计算平均flg_vpip
    4. 链接到 id_hand 上数百万行的分组结果

    你能把表格分成几个单独的表格吗?您的字段究竟是什么意思?示例手牌是什么样的?

    您至少需要 id_player、id_hand、position 和 cnt_players 的索引。

    在索引中包含所有字段可能会更好。我不确定 postgresql,但如果查询所需的所有数据都在索引中,SQL Server 可以跳过加载实际的表数据页。所以如果你有一个位置索引,cnt_players、id_player 和 flg_vpip,你最里面的选择可能会快很多。

    如果您不打算经常运行查询,我认为更好的方法是提前将这些内部选择计算到一个或两个表中。

    select id_player, position, cnt_players,
        ROUND(avg(flg_vpip::int)*25) AS avg_vpip
    into auxtable
    from holdem oldem
    group by id_player, position, cnt_players
    
    alter table auxtable add constraint PK_auxtable 
        primary key clustered (id_player, position, cnt_players)
    

    像这样:

    SELECT sbvpip*4 as smallbvpip,btnvpip*4 as buttonvpip, sum(amt_won)*400/count(*) AS winrate, count(*) as count
    FROM holdem
        JOIN (
            SELECT id_player AS pid2, id_hand AS hid, sbvpip
            FROM holdem
                JOIN auxtable ON auxtable.id_payer = holdem.id_player 
                    and auxtable.position = holdem.position
                    and auxtable.cnt_players = holdem.cnt_players
            WHERE holdem.position = 8 AND holdem.cnt_players = 6
        ) AS auxtable2 ON hid = id_hand
    

    【讨论】:

    • 覆盖索引在 Postgres 中没有帮助。不幸的是,它没有“仅索引扫描”