非常慢的简单 JOIN 查询

Lar*_*ars 12 postgresql performance postgresql-9.6 query-performance

简单的数据库结构(用于在线论坛):

CREATE TABLE users (
    id integer NOT NULL PRIMARY KEY,
    username text
);
CREATE INDEX ON users (username);

CREATE TABLE posts (
    id integer NOT NULL PRIMARY KEY,
    thread_id integer NOT NULL REFERENCES threads (id),
    user_id integer NOT NULL REFERENCES users (id),
    date timestamp without time zone NOT NULL,
    content text
);
CREATE INDEX ON posts (thread_id);
CREATE INDEX ON posts (user_id);
Run Code Online (Sandbox Code Playgroud)

表中大约有 8users万个条目和 260 万个条目posts。这个通过帖子获得前 100 位用户的简单查询需要2.4 秒

EXPLAIN ANALYZE SELECT u.id, u.username, COUNT(p.id) AS PostCount FROM users u
                    INNER JOIN posts p on p.user_id = u.id
                    WHERE u.username IS NOT NULL
                    GROUP BY u.id
ORDER BY PostCount DESC LIMIT 100;
Run Code Online (Sandbox Code Playgroud)
CREATE TABLE users (
    id integer NOT NULL PRIMARY KEY,
    username text
);
CREATE INDEX ON users (username);

CREATE TABLE posts (
    id integer NOT NULL PRIMARY KEY,
    thread_id integer NOT NULL REFERENCES threads (id),
    user_id integer NOT NULL REFERENCES users (id),
    date timestamp without time zone NOT NULL,
    content text
);
CREATE INDEX ON posts (thread_id);
CREATE INDEX ON posts (user_id);
Run Code Online (Sandbox Code Playgroud)

随着set enable_seqscan = false更糟糕:

EXPLAIN ANALYZE SELECT u.id, u.username, COUNT(p.id) AS PostCount FROM users u
                    INNER JOIN posts p on p.user_id = u.id
                    WHERE u.username IS NOT NULL
                    GROUP BY u.id
ORDER BY PostCount DESC LIMIT 100;
Run Code Online (Sandbox Code Playgroud)

usernamePostgres 中缺少Group by ,因为它不是必需的(SQL Server 说username如果我想选择用户名,我必须分组)。分组与username在 Postgres 上的执行时间增加了一点点毫秒或什么都不做。

对于科学,我已将 Microsoft SQL Server 安装到同一台服务器(运行 archlinux、8 核 xeon、24 gb ram、ssd)并从 Postgres 迁移所有数据 -相同的表结构、相同的索引、相同的数据。获取前 100 张海报的相同查询在0.3 秒内运行:

SELECT TOP 100 u.id, u.username, COUNT(p.id) AS PostCount FROM dbo.users u
                    INNER JOIN dbo.posts p on p.user_id = u.id
                    WHERE u.username IS NOT NULL
                    GROUP BY u.id, u.username
ORDER BY PostCount DESC
Run Code Online (Sandbox Code Playgroud)

从相同的数据产生相同的结果,但速度提高 8 倍。它是 Linux 上 MS SQL 的测试版,我想在它的“家庭”操作系统 - Windows Server 上运行 - 它可能会更快。

我的 PostgreSQL 查询是完全错误的,还是 PostgreSQL 很慢?

附加信息

版本几乎是最新的(9.6.1,目前最新的是9.6.2,ArchLinux只是包过时,更新很慢)。配置:

max_connections = 75
shared_buffers = 3584MB       
effective_cache_size = 10752MB
work_mem = 24466kB         
maintenance_work_mem = 896MB   
dynamic_shared_memory_type = posix  
min_wal_size = 1GB
max_wal_size = 2GB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
Run Code Online (Sandbox Code Playgroud)

EXPLAIN ANALYZE输出:https : //pastebin.com/HxucRgnk

尝试了所有索引,甚至使用了 GIN 和 GIST,PostgreSQL 的最快方法(谷歌搜索确认了很多行)是使用顺序扫描。

MS SQL Server 14.0.405.200-1,默认配置。

我在 API 中使用它(使用没有分析的简单选择),并用 chrome 调用这个 API 端点,它说它需要 2500 毫秒 +-,增加 50 毫秒的 HTTP 和 Web 服务器开销(API 和 SQL 在同一台服务器上运行) - 一样的。我不在乎这里或那里的 100 毫秒,我关心的是整整两秒。

explain analyze SELECT user_id, count(9) FROM posts group by user_id;需要 700 毫秒。posts表的大小是 2154 MB。

小智 8

这可能有效,也可能无效 - 我的直觉是它在组和过滤器之前加入您的表。我建议尝试以下操作:在尝试加入之前使用 CTE 过滤和分组:

with
    __posts as(
        select
            user_id,
            count(1) as num_posts
        from
            posts
        group by
            user_id
        order by
            num_posts desc
        limit 100
    )
select
    users.username,
    __posts.num_posts
from
    users
    inner join __posts on(
        __posts.user_id = users.id
    )
order by
    num_posts desc
Run Code Online (Sandbox Code Playgroud)

查询规划器有时只需要一点指导。这个解决方案在这里很有效,但在某些情况下 CTE 可能很糟糕。CTE 专门存储在内存中。因此,大数据返回可能会超过 Postgres 分配的内存并开始交换(MS 中的分页)。CTE 也无法编入索引,因此在查询 CTE 时,足够大的查询仍可能导致显着减慢。

您可以真正接受的最佳建议是尝试多种方法并检查您的查询计划。


小智 1

另一个好的查询变体是:

SELECT p.user_id, p.cnt AS PostCount
FROM users u
INNER JOIN (
    select user_id, count(id) as cnt from posts group by user_id
) as p on p.user_id = u.id
WHERE u.username IS NOT NULL          
ORDER BY PostCount DESC LIMIT 100;
Run Code Online (Sandbox Code Playgroud)

它不会利用 CTE 并给出正确的答案(理论上 CTE 示例可能会生成少于 100 行,因为它首先限制然后与用户连接)。

我想,MSSQL 能够在其查询优化器中执行此类转换,而 PostgreSQL 无法在连接下下推聚合。或者 MSSQL 只是有更快的哈希连接实现。