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

时间:2011-07-05 21:22:22

标签: 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)

现在,我想检索您的朋友(一组用户)在一系列地方存放的评分

我写的自然查询是:

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驱动器。应该足够了: - )

修改

Per 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毫秒。这仍然是使用临时表的两倍慢,但是制作表的开销否定了我看到的任何增益。我仍然不明白如何在btree索引中执行O(500)查找可能需要十秒钟,但哈希索引无疑要快得多。

1 个答案:

答案 0 :(得分:1)

看起来它正在占据索引中的每一行,然后扫描你的user_id数组,然后如果它发现它通过你的place_id数组进行扫描。这意味着,对于300万行,它必须扫描100 user_id秒,并且对于每个匹配,它扫描10,000 place_id秒。这些匹配单独快速,但这是一个糟糕的算法,可能导致高达300亿次操作。

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