sql - 使用 PostgreSQL 从稀疏表中检索两列、批量、随机访问

标签 sql postgresql

我在 PostgreSQL 中存储了相对合理(约 300 万)数量的非常小的行(整个数据库约 300MB)。数据是这样组织的:

                                      Table "public.tr_rating"
  Column   |           Type           |                           Modifiers                           
-----------+--------------------------+---------------------------------------------------------------
 user_id   | bigint                   | not null
 place_id  | bigint                   | not null
 rating    | smallint                 | not null
 rated_at  | timestamp with time zone | not null default now()
 rating_id | bigint                   | not null default nextval('tr_rating_rating_id_seq'::regclass)
Indexes:
    "tr_rating_rating_id_key" UNIQUE, btree (rating_id)
    "tr_rating_user_idx" btree (user_id, place_id)

现在,我想检索您的 friend (一组用户)对一组地点的评分

我写的自然查询是:

SELECT * FROM tr_rating WHERE user_id=ANY(?) AND place_id=ANY(?)

user_id 数组的大小约为 500,而 place_id 数组的大小约为 10,000

这变成:

 Bitmap Heap Scan on tr_rating  (cost=2453743.43..2492013.53 rows=3627 width=34) (actual time=10174.044..10174.234 rows=1111 loops=1)
 Buffers: shared hit=27922214
     ->  Bitmap Index Scan on tr_rating_user_idx  (cost=0.00..2453742.53 rows=3627 width=0) (actual time=10174.031..10174.031 rows=1111 loops=1)
         Index Cond: ((user_id = ANY (...) ))
         Buffers: shared hit=27922214
 Total runtime: 10279.290 ms

我在这里看到的第一个可疑之处是它估计为 500 个用户扫描索引将需要 2.5M 磁盘寻道

这里的其他一切看起来都很合理,只是需要整整十秒才能做到这一点!索引(通过 \di)看起来像:

 public | tr_rating_user_idx | index | tr_rating | 67 MB | 

在 67 MB 时,我预计它可以在很短的时间内撕毁索引,即使它必须按顺序进行。正如 EXPLAIN ANALYZE 中的缓冲区统计所示,所有内容都已在内存中(因为除 shared_hit 之外的所有值均为零,因此被抑制)。

我尝试了 REINDEXVACUUMANALYZECLUSTER 的各种组合,但没有明显的改进。

关于我在这里做错了什么,或者我如何进一步调试有什么想法吗?我很困惑; 67MB 的数据对于花费如此多的时间搜索来说是微不足道的......

作为引用,硬件是 8 路最新 Xeon,在 RAID-10 中具有 8 个 15K 300GB 驱动器。应该足够了:-)

编辑

根据 btilly 的建议,我尝试了临时表:

 => explain analyze select * from tr_rating NATURAL JOIN user_ids NATURAL JOIN place_ids;
                                                                      QUERY PLAN                                                                      
------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=49133.46..49299.51 rows=3524 width=34) (actual time=13.801..15.676 rows=1111 loops=1)
   Hash Cond: (place_ids.place_id = tr_rating.place_id)
   ->  Seq Scan on place_ids  (cost=0.00..59.66 rows=4066 width=8) (actual time=0.009..0.619 rows=4251 loops=1)
   ->  Hash  (cost=48208.02..48208.02 rows=74035 width=34) (actual time=13.767..13.767 rows=7486 loops=1)
         Buckets: 8192  Batches: 1  Memory Usage: 527kB
         ->  Nested Loop  (cost=0.00..48208.02 rows=74035 width=34) (actual time=0.047..11.055 rows=7486 loops=1)
               ->  Seq Scan on user_ids  (cost=0.00..31.40 rows=2140 width=8) (actual time=0.006..0.399 rows=2189 loops=1)
               ->  Index Scan using tr_rating_user_idx on tr_rating  (cost=0.00..22.07 rows=35 width=34) (actual time=0.002..0.003 rows=3 loops=2189)
                     Index Cond: (tr_rating.user_id = user_ids.user_id) JOIN place_ids;
 Total runtime: 15.931 ms

为什么查询计划在面对临时表而不是数组时要好得多?数据完全相同,只是以不同的方式呈现。此外,我测量了在运行时创建临时表的时间在几十到几百毫秒内,这是一笔相当高的开销。我可以继续使用数组方法,但允许 Postgres 使用更快的散列连接吗?

编辑 2

通过在 user_id 上创建哈希索引,运行时间减少到 250 毫秒。将另一个哈希索引添加到 place_id 可将运行时间进一步减少到 50 毫秒。这仍然比使用临时表慢两倍,但是制作表的开销抵消了我看到的任何 yield 。我仍然不明白在 btree 索引中进行 O(500) 查找怎么可能需要十秒,但哈希索引无疑要快得多。

最佳答案

看起来它正在获取索引中的每一行,然后扫描您的 user_id 数组,然后如果它发现它扫描您的 place_id 数组。这意味着对于 300 万行,它必须扫描 100 个 user_id,并且对于每个匹配项,它扫描 10,000 个 place_id。这些匹配项各自都很快,但这是一个糟糕的算法,可能会导致多达 300 亿次操作。

您最好创建两个临时表,为它们提供索引,然后进行连接。如果它执行散列连接,那么您可能会进行 600 万次散列查找。 (user_id 为 300 万,place_id 为 300 万。)

关于sql - 使用 PostgreSQL 从稀疏表中检索两列、批量、随机访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6589028/

相关文章:

java - 如何通过 DataAccessException 检索 postgres 数据库上的 SQL 错误代码?

postgresql - Ebean 中的 Heroku Postgres bytea

postgresql - 如何使用 Sequelize 将 id 传递给其关联记录

sql - 如何将空表的 MAX() 视为 0 而不是 NULL

MySql :- Count duplicate records including foreign key relation

从一个数据库到另一个数据库的sqlite数据

postgresql - 转储 postgres 数据库、时间和 .sql 文件权重

postgresql - 处理来自 jsonb_each 查询的记录类型

sql - 如何更改 Oracle TRUNC 用于开始一周的日期

mysql - 使用 where 子句更新或插入