Postgres is ignoring a timestamp index, why?

蓝咒 提交于 2021-02-10 05:46:33

问题


I have the following tables:

users (id, network_id)
networks (id)
private_messages (id, sender_id, receiver_id, created_at)

I have indexes on users.network_id, and all 3 columns in private messages however the query is skipping the indexes and taking a very long time to run. Any ideas what is wrong in the query that is causing the index to be skipped?

EXPLAIN ANALYZE SELECT COUNT(*) 
FROM "networks" 
WHERE (
          networks.created_at BETWEEN ((timestamp '2013-01-01')) AND (( (timestamp '2013-01-31') + interval '-1 second'))
          AND (SELECT COUNT(*) FROM private_messages INNER JOIN users ON private_messages.receiver_id = users.id WHERE users.network_id = networks.id AND (private_messages.created_at BETWEEN ((timestamp '2013-03-01')) AND (( (timestamp '2013-03-31') + interval '-1 second'))) ) > 0)

Result:

Aggregate  (cost=722675247.10..722675247.11 rows=1 width=0) (actual time=519916.108..519916.108 rows=1 loops=1)
  ->  Seq Scan on networks  (cost=0.00..722675245.34 rows=703 width=0) (actual time=2576.205..519916.044 rows=78 loops=1)
        Filter: ((created_at >= '2013-01-01 00:00:00'::timestamp without time zone) AND (created_at <= '2013-01-30 23:59:59'::timestamp without time zone) AND ((SubPlan 1) > 0))
        SubPlan 1
          ->  Aggregate  (cost=50671.34..50671.35 rows=1 width=0) (actual time=240.359..240.359 rows=1 loops=2163)
                ->  Hash Join  (cost=10333.69..50671.27 rows=28 width=0) (actual time=233.997..240.340 rows=13 loops=2163)
                      Hash Cond: (private_messages.receiver_id = users.id)
                      ->  Bitmap Heap Scan on private_messages  (cost=10127.11..48675.15 rows=477136 width=4) (actual time=56.599..232.855 rows=473686 loops=1809)
                            Recheck Cond: ((created_at >= '2013-03-01 00:00:00'::timestamp without time zone) AND (created_at <= '2013-03-30 23:59:59'::timestamp without time zone))
                            ->  Bitmap Index Scan on index_private_messages_on_created_at  (cost=0.00..10007.83 rows=477136 width=0) (actual time=54.551..54.551 rows=473686 loops=1809)
                                  Index Cond: ((created_at >= '2013-03-01 00:00:00'::timestamp without time zone) AND (created_at <= '2013-03-30 23:59:59'::timestamp without time zone))
                      ->  Hash  (cost=205.87..205.87 rows=57 width=4) (actual time=0.218..0.218 rows=2 loops=2163)
                            Buckets: 1024  Batches: 1  Memory Usage: 0kB
                            ->  Index Scan using index_users_on_network_id on users  (cost=0.00..205.87 rows=57 width=4) (actual time=0.154..0.215 rows=2 loops=2163)
                                  Index Cond: (network_id = networks.id)
Total runtime: 519916.183 ms

Thank you.


回答1:


Let's try something different. I am only suggesting this as an "answer" because of its length and you cannot format a comment. Let's approach the query modularly as a series of subsets that need to get intersected. Let's see how long it takes each of these to execute (please report). Substitute your timestamps for t1 and t2. Note how each query builds upon the prior one, making the prior one an "inline view".

EDIT: also, please confirm the columns in the Networks table.

1

 select PM.receiver_id from private_messages PM
 where PM.create_at between (t1 and t2)

2

 select U.id, U.network_id from users U
 join
 (
   select PM.receiver_id from private_messages PM 
   where PM.create_at between (t1 and t2)
 ) as FOO
 on U.id = FOO.receiver_id

3

select N.* from networks N
join
(
select U.id, U.network_id from users U
 join
 (
   select PM.receiver_id from private_messages PM 
   where PM.create_at between (t1 and t2)
 ) as FOO
 on U.id = FOO.receiver_id
) as BAR
on N.id = BAR.network_id



回答2:


First, I think you want an index on network.created_at, even though right now with over 10% of the table matching the WHERE, it probably won't be used.

Next, I expect you will get better speed if you try to get as much logic as possible into one query, instead of splitting some into a subquery. I believe the plan is indicating iterating over each value of network.id that matches; usually an all-at-once join works better.

I think the code below is logically equivalent. If not, close.

SELECT COUNT(*) 
FROM 
 (SELECT users.network_id FROM "networks"
  JOIN users 
  ON users.network_id = networks.id
  JOIN private_messages
  ON private_messages.receiver_id = users.id
   AND (private_messages.created_at 
    BETWEEN ((timestamp '2013-03-01')) 
         AND (( (timestamp '2013-03-31') + interval '-1 second')))
  WHERE 
   networks.created_at 
    BETWEEN ((timestamp '2013-01-01')) 
     AND (( (timestamp '2013-01-31') + interval '-1 second'))
  GROUP BY users.network_id)
     AS main_subquery  
;

My experience is that you will get the same query plan if you move the networks.created_at into the ON clause for the users-networks join. I don't think your issue is timestamps; it's the structure of the query. You may also get a better (or worse) plan by replacing the GROUP BY in the subquery with SELECT DISTINCT users.network_id.



来源:https://stackoverflow.com/questions/15977741/postgres-is-ignoring-a-timestamp-index-why

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!