我在生产(Linux)和开发(Mac)
中运行PG 9.6.1我正在开发和生产中测试相同的查询。在开发过程中,我测试了生产数据库的多个快照(仅相隔几分钟)。
出于某种原因,同样的查询提供了截然不同的查询计划,导致生产的计划性能很差。
当我执行以下查询时
SELECT DISTINCT ON (v0."email") v0."id"
,v0."first_seen" ,v0."last_seen" ,v0."last_heard_from" ,v0."last_contacted" ,v0."last_page_viewed" ,v0."sessions" ,v0."seen_welcome_message" ,v0."signed_up_at" ,v0."referrer" ,v0."unsubscribed" ,v0."merged" ,v0."marked_at" ,v0."first_name" ,v0."last_name" ,v0."email" ,v0."slug" ,v0."name" ,v0."type" ,v0."avatar" ,v0."user_id" ,v0."location" ,v0."app_id" ,v0."actor_id" ,v0."marked_by_id" ,v0."inserted_at" ,v0."updated_at"
FROM "visitors" AS v0
INNER JOIN "auto_messages" AS a1 ON (a1."app_id" = v0."app_id")
AND (a1."id" = $1)
INNER JOIN LATERAL(SELECT * FROM find_matching_visitors(a1."app_id", a1."formatted_default_filters", a1."formatted_custom_filters")) AS f2 ON f2."id" = v0."id"
WHERE (
CASE
WHEN a1."window_enabled"
THEN extract(dow FROM current_timestamp at TIME zone COALESCE((v0."location" - >> 'timezone'), 'Europe/Paris')) = ANY (a1."window_days_of_week"::INT [])
AND CURRENT_TIME at TIME zone COALESCE((v0."location" - >> 'timezone'), 'Europe/Paris') BETWEEN a1."window_start"
AND a1."window_end"
ELSE true
END
)
AND (
NOT EXISTS (
SELECT *
FROM auto_message_events
WHERE auto_message_id = a1."id"
AND (
visitor_id = v0."id"
OR visitor_user_id = v0."user_id"
OR visitor_email = v0."email"
)
AND event = 'sent'
)
)
AND ((v0."type" = 'user') OR (v0."type" = 'lead'))
AND (NOT (v0."merged"))
AND (v0."marked_at" IS NULL)
AND (NOT (v0."email" IS NULL) AND NOT (v0."unsubscribed"))
AND ((a1."status" = $2) AND (a1."channel" = $3))
GROUP BY v0."id"
ORDER BY v0."email"
,v0."last_seen" DESC LIMIT $4 [446, "live", "email", 500]
在制作中,我获得以下查询计划
Limit (cost=1085.72..1085.73 rows=1 width=1070) (actual time=16862.966..16862.966 rows=0 loops=1)
.16862.964 rows=0 loops=1)72..1085.73 rows=1 width=1070) (actual time=16862.964.--More--
-> Sort (cost=1085.72..1085.73 rows=1 width=1070) (actual time=16862.963..16862.963 rows=0 loops=1)
Sort Key: v0.email, v0.last_seen DESC
Sort Method: quicksort Memory: 25kB
-> Group (cost=1085.71..1085.71 rows=1 width=1070) (actual time=16862.946..16862.946 rows=0 loops=1)
Group Key: v0.id
-> Sort (cost=1085.71..1085.71 rows=1 width=1070) (actual time=16862.936..16862.936 rows=0 loops=1)
Sort Key: v0.id
Sort Method: quicksort Memory: 25kB
-> Nested Loop Anti Join (cost=890.31..1085.70 rows=1 width=1070) (actual time=16862.922..16862.922 rows=0 loops=1)
Join Filter: (auto_message_events.auto_message_id = a1.id)
-> Nested Loop (cost=885.54..1072.87 rows=1 width=1074) (actual time=46.013..16856.367 rows=107 loops=1)
Join Filter: ((v0.id)::text = (find_matching_visitors.id)::text)
Rows Removed by Join Filter: 112824
-> Nested Loop (cost=885.29..1050.12 rows=1 width=1162) (actual time=4.020..14.710 rows=949 loops=1)
-> Index Scan using auto_messages_pkey on auto_messages a1 (cost=0.27..8.30 rows=1 width=122) (actual time=0.019..0.021 rows=1 loops=1)
Index Cond: (id = 435)
Filter: (((status)::text = 'live'::text) AND ((channel)::text = 'email'::text))
-> Bitmap Heap Scan on visitors v0 (cost=885.02..1041.81 rows=1 width=1070) (actual time=3.995..13.180 rows=949 loops=1)
Recheck Cond: ((email IS NOT NULL) AND ((app_id)::text = (a1.app_id)::text))
Filter: ((NOT merged) AND (marked_at IS NULL) AND (NOT unsubscribed) AND (((type)::text = 'user'::text) OR ((type)::text = 'lead'::text)) AND CASE WHEN a1.window_enabled THEN ((date_part('dow'::text, timezone(COALESCE((location ->> 'timezone'::text), 'Europe/Paris'::text), now())) = ANY ((a1.window_days_of_week)::double precision[])) AND (timezone(COALESCE((location ->> 'timezone'::text), 'Europe/Paris'::text), ('now'::cstring)::time with time zone) >= (a1.window_start)::time with time zone) AND (timezone(COALESCE((location ->> 'timezone'::text), 'Europe/Paris'::text), ('now'::cstring)::time with time zone) <= (a1.window_end)::time with time zone)) ELSE true END)
Rows Removed by Filter: 56
Heap Blocks: exact=624
-> BitmapAnd (cost=885.02..885.02 rows=39 width=0) (actual time=3.874..3.874 rows=0 loops=1)
-> Bitmap Index Scan on email_idx (cost=0.00..284.01 rows=6344 width=0) (actual time=2.161..2.161 rows=9749 loops=1)
Index Cond: (email IS NOT NULL)
-> Bitmap Index Scan on visitors_app_id_signed_up_index (cost=0.00..600.68 rows=6950 width=0) (actual time=1.086..1.086 rows=4502 loops=1)
Index Cond: ((app_id)::text = (a1.app_id)::text)
-> Function Scan on find_matching_visitors (cost=0.25..10.25 rows=1000 width=32) (actual time=17.681..17.706 rows=119 loops=949)
-> Bitmap Heap Scan on auto_message_events (cost=4.77..8.79 rows=1 width=74) (actual time=0.051..0.051 rows=1 loops=107)
Recheck Cond: (((auto_message_id = 435) AND ((visitor_id)::text = (v0.id)::text) AND ((event)::text = 'sent'::text)) OR ((auto_message_id = 435) AND ((event)::text = 'sent'::text) AND ((visitor_user_id)::text = (v0.user_id)::text)) OR ((auto_message_id = 435) AND ((event)::text = 'sent'::text) AND ((visitor_email)::text = (v0.email)::text)))
Heap Blocks: exact=107
-> BitmapOr (cost=4.77..4.77 rows=1 width=0) (actual time=0.044..0.044 rows=0 loops=107)
-> Bitmap Index Scan on auto_message_events_auto_message_id_visitor_id_event_index (cost=0.00..3.56 rows=1 width=0) (actual time=0.019..0.019 rows=1 loops=107)
Index Cond: ((auto_message_id = 435) AND ((visitor_id)::text = (v0.id)::text) AND ((event)::text = 'sent'::text))
-> Bitmap Index Scan on auto_message_id_event_visitor_user_id_idx (cost=0.00..0.59 rows=1 width=0) (actual time=0.008..0.008 rows=1 loops=107)
Index Cond: ((auto_message_id = 435) AND ((event)::text = 'sent'::text) AND ((visitor_user_id)::text = (v0.user_id)::text))
-> Bitmap Index Scan on auto_message_id_event_visitor_email_idx (cost=0.00..0.62 rows=1 width=0) (actual time=0.008..0.008 rows=1 loops=107)
Index Cond: ((auto_message_id = 435) AND ((event)::text = 'sent'::text) AND ((visitor_email)::text = (v0.email)::text))
Planning time: 2.566 ms
Execution time: 16863.358 ms
在开发(生产数据库快照)
中运行的完全相同的查询 Limit (cost=417.94..417.95 rows=1 width=2096) (actual time=90.445..90.445 rows=0 loops=1)
-> Unique (cost=417.94..417.95 rows=1 width=2096) (actual time=90.444..90.444 rows=0 loops=1)
-> Sort (cost=417.94..417.95 rows=1 width=2096) (actual time=90.443..90.443 rows=0 loops=1)
Sort Key: v0.email, v0.last_seen DESC
Sort Method: quicksort Memory: 25kB
-> Group (cost=417.92..417.93 rows=1 width=2096) (actual time=90.435..90.435 rows=0 loops=1)
Group Key: v0.id
-> Sort (cost=417.92..417.93 rows=1 width=2096) (actual time=90.435..90.435 rows=0 loops=1)
Sort Key: v0.id
Sort Method: quicksort Memory: 25kB
-> Nested Loop Anti Join (cost=388.06..417.91 rows=1 width=2096) (actual time=90.430..90.430 rows=0 loops=1)
Join Filter: (auto_message_events.auto_message_id = a1.id)
-> Nested Loop (cost=375.05..396.85 rows=1 width=2100) (actual time=87.181..87.341 rows=76 loops=1)
-> Index Scan using auto_messages_pkey on auto_messages a1 (cost=0.28..8.30 rows=1 width=120) (actual time=0.015..0.017 rows=1 loops=1)
Index Cond: (id = 435)
Filter: (((status)::text = 'live'::text) AND ((channel)::text = 'email'::text))
-> Hash Join (cost=374.78..388.54 rows=1 width=2096) (actual time=87.163..87.292 rows=76 loops=1)
Hash Cond: ((find_matching_visitors.id)::text = (v0.id)::text)
-> Function Scan on find_matching_visitors (cost=0.25..10.25 rows=1000 width=32) (actual time=79.139..79.151 rows=81 loops=1)
-> Hash (cost=374.52..374.52 rows=1 width=2096) (actual time=8.008..8.008 rows=911 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 468kB
-> Bitmap Heap Scan on visitors v0 (cost=230.95..374.52 rows=1 width=2096) (actual time=5.024..6.986 rows=911 loops=1)
Recheck Cond: (((app_id)::text = (a1.app_id)::text) AND (email IS NOT NULL))
Filter: ((NOT merged) AND (marked_at IS NULL) AND (NOT unsubscribed) AND (((type)::text = 'user'::text) OR ((type)::text = 'lead'::text)) AND CASE WHEN a1.window_enabled THEN ((date_part('dow'::text, timezone(COALESCE((location ->> 'timezone'::text), 'Europe/Paris'::text), now())) = ANY ((a1.window_days_of_week)::double precision[])) AND (timezone(COALESCE((location ->> 'timezone'::text), 'Europe/Paris'::text), ('now'::cstring)::time with time zone) >= (a1.window_start)::time with time zone) AND (timezone(COALESCE((location ->> 'timezone'::text), 'Europe/Paris'::text), ('now'::cstring)::time with time zone) <= (a1.window_end)::time with time zone)) ELSE true END)
Rows Removed by Filter: 43
Heap Blocks: exact=1094
-> BitmapAnd (cost=230.95..230.95 rows=36 width=0) (actual time=4.804..4.804 rows=0 loops=1)
-> Bitmap Index Scan on visitors_app_id_signed_up_index (cost=0.00..101.44 rows=2802 width=0) (actual time=1.044..1.044 rows=4670 loops=1)
Index Cond: ((app_id)::text = (a1.app_id)::text)
-> Bitmap Index Scan on email_idx (cost=0.00..129.26 rows=6512 width=0) (actual time=3.313..3.313 rows=20201 loops=1)
Index Cond: (email IS NOT NULL)
-> Bitmap Heap Scan on auto_message_events (cost=13.01..17.03 rows=1 width=73) (actual time=0.039..0.039 rows=1 loops=76)
Recheck Cond: (((auto_message_id = 435) AND ((visitor_id)::text = (v0.id)::text) AND ((event)::text = 'sent'::text)) OR ((auto_message_id = 435) AND ((event)::text = 'sent'::text) AND ((visitor_user_id)::text = (v0.user_id)::text)) OR ((auto_message_id = 435) AND ((event)::text = 'sent'::text) AND ((visitor_email)::text = (v0.email)::text)))
Heap Blocks: exact=76
-> BitmapOr (cost=13.01..13.01 rows=1 width=0) (actual time=0.037..0.037 rows=0 loops=76)
-> Bitmap Index Scan on auto_message_events_auto_message_id_visitor_id_event_index (cost=0.00..4.43 rows=1 width=0) (actual time=0.011..0.011 rows=1 loops=76)
Index Cond: ((auto_message_id = 435) AND ((visitor_id)::text = (v0.id)::text) AND ((event)::text = 'sent'::text))
-> Bitmap Index Scan on auto_message_id_event_visitor_user_id_idx (cost=0.00..4.29 rows=1 width=0) (actual time=0.011..0.011 rows=1 loops=76)
Index Cond: ((auto_message_id = 435) AND ((event)::text = 'sent'::text) AND ((visitor_user_id)::text = (v0.user_id)::text))
-> Bitmap Index Scan on auto_message_id_event_visitor_email_idx (cost=0.00..4.29 rows=1 width=0) (actual time=0.014..0.014 rows=0 loops=76)
Index Cond: ((auto_message_id = 435) AND ((event)::text = 'sent'::text) AND ((visitor_email)::text = (v0.email)::text))
Planning time: 2.848 ms
Execution time: 90.722 ms
更新
事实证明,从生产中的查询中删除表达式(NOT (v0."email" IS NULL)
,性能会自行纠正。
为什么有任何想法? (该字段已编入索引)