我们有一个images
表,其中包含约2500万条记录,当我根据多个联接的值查询该表时,规划师的估计与行数的实际结果有很大不同。在没有所有联接的情况下,我们还有其他大致相同的查询,并且查询要快得多。我想知道我可以采取哪些步骤来调试和优化查询。另外,最好有一个索引覆盖联接和where
子句中包含的所有列,或者为每个联接列分配一个索引,然后为where
子句中的所有字段提供多个索引?
查询:
EXPLAIN ANALYZE
SELECT "images".* FROM "images"
INNER JOIN "locations" ON "locations"."id" = "images"."location_id"
INNER JOIN "users" ON "images"."creator_id" = "users"."id"
INNER JOIN "user_groups" ON "users"."id" = "user_groups"."user_id"
WHERE "images"."deleted_at" IS NULL
AND "user_groups"."group_id" = 7
AND "images"."creator_type" = 'User'
AND "images"."status" = 2
AND "locations"."active" = TRUE
ORDER BY date_uploaded DESC
LIMIT 50
OFFSET 0;
说明:
Limit (cost=25670.61..25670.74 rows=50 width=585) (actual time=1556.250..1556.278 rows=50 loops=1)
-> Sort (cost=25670.61..25674.90 rows=1714 width=585) (actual time=1556.250..1556.264 rows=50 loops=1)
Sort Key: images.date_uploaded
Sort Method: top-N heapsort Memory: 75kB
-> Nested Loop (cost=1.28..25613.68 rows=1714 width=585) (actual time=0.097..1445.777 rows=160886 loops=1)
-> Nested Loop (cost=0.85..13724.04 rows=1753 width=585) (actual time=0.069..976.326 rows=161036 loops=1)
-> Nested Loop (cost=0.29..214.87 rows=22 width=8) (actual time=0.023..0.786 rows=22 loops=1)
-> Seq Scan on user_groups (cost=0.00..95.83 rows=22 width=4) (actual time=0.008..0.570 rows=22 loops=1)
Filter: (group_id = 7)
Rows Removed by Filter: 5319
-> Index Only Scan using users_pkey on users (cost=0.29..5.40 rows=1 width=4) (actual time=0.006..0.008 rows=1 loops=22)
Index Cond: (id = user_groups.user_id)
Heap Fetches: 18
-> Index Scan using creator_date_uploaded_Where_pub_not_del on images (cost=0.56..612.08 rows=197 width=585) (actual time=0.062..40.992 rows=7320 loops=22)
Index Cond: ((creator_id = users.id) AND ((creator_type)::text = 'User'::text) AND (status = 2))
-> Index Scan using locations_pkey on locations (cost=0.43..6.77 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=161036)
Index Cond: (id = images.location_id)
Filter: active
Rows Removed by Filter: 0
Planning time: 1.694 ms
Execution time: 1556.352 ms
我们正在RDS db.m4.large实例上运行Postgres 9.4。
答案 0 :(得分:2)
对于查询本身,您唯一可以做的就是跳过users
表。在EXPLAIN
中,您可以看到它仅执行Index Only Scan
,而没有实际触摸桌子。因此,从技术上讲,您的查询可能如下所示:
SELECT images.* FROM images
INNER JOIN locations ON locations.id = images.location_id
INNER JOIN user_groups ON images.creator_id = user_groups.user_id
WHERE images.deleted_at IS NULL
AND user_groups.group_id = 7
AND images.creator_type = 'User'
AND images.status = 2
AND locations.active = TRUE
ORDER BY date_uploaded DESC
OFFSET 0 LIMIT 50
其余内容与索引有关。 locations
似乎只有很少的数据,因此此处的优化不会为您带来任何好处。另一方面,user_groups
可以受益于索引ON (user_id) WHERE group_id = 7
或ON (group_id, user_id)
。这应该删除对表内容的一些额外过滤。
-- Option 1
CREATE INDEX ix_usergroups_userid_groupid7
ON user_groups (user_id)
WHERE group_id = 7;
-- Option 2
CREATE INDEX ix_usergroups_groupid_userid
ON user_groups (group_id, user_id);
当然,这里最大的是images
。当前,刨床将对creator_date_uploaded_Where_pub_not_del
进行索引扫描,我怀疑这不完全符合要求。在这里,您会根据自己的使用方式想到多个选项-从一种很常见的搜索参数开始:
-- Option 1
CREATE INDEX ix_images_creatorid_typeuser_status2_notdel
ON images (creator_id)
WHERE creator_type = 'User' AND status = 2 AND deleted_at IS NULL;
具有完全动态参数的
-- Option 2
CREATE INDEX ix_images_status_creatortype_creatorid_notdel
ON images (status, creator_type, creator_id)
WHERE deleted_at IS NULL;
首选第一个索引,因为它较小(值被过滤掉而不是被索引)。
总而言之,除非受到内存(或其他因素)的限制,否则我将在user_groups
和images
上添加索引。索引的正确选择必须凭经验进行确认,因为通常有多种选择,而且情况取决于数据的统计分布。
答案 1 :(得分:0)
这是另一种方法:
我认为问题之一是您进行了1714次连接,然后仅返回前50个结果。我们可能希望尽快避免额外的联接。
为此,我们将尝试首先按date_uploaded建立索引。然后,我们将按其余列进行过滤。另外,我们添加creator_id以获取仅索引扫描:
CREATE INDEX ix_images_sort_test
ON images (date_uploaded desc, creator_id)
WHERE creator_type = 'User' AND status = 2 AND deleted_at IS NULL;
您也可以使用通用版本(未过滤)。但这应该更糟。由于第一列将是date_uploaded,因此我们需要读取整个索引以过滤其余列。
CREATE INDEX ix_images_sort_test
ON images (date_uploaded desc, status, creator_type, creator_id)
WHERE deleted_at IS NULL;
这里很可惜的是您还按了另一个表上的group_id进行过滤。即便如此,尝试这种方法还是值得的。
还要验证所有联接的表在外键上都有索引。
因此,为user_groups添加一个索引为(user_id,group_id)
而且,正如鲍里斯(Boris)所注意到的,您可以删除“用户”联接。