SELECT "user".id as user_id, "user".username as user_name, (SELECTcount(project.id) AS cnt FROM project WHERE project.user_id = "user".id AND project.deleted =0) AS user_projects_cnt, coalesce((SELECT user_login_info.created_utc FROM user_login_info WHERE user_login_info.user_id = "user".id ORDERBY user_login_info.id DESC LIMIT 1), "user".id) AS default_login_time FROM "user" WHERE "user".deleted =0ORDERBY default_login_time DESC, "user".ext_id DESC LIMIT 10OFFSET0
Sort (cost=750000347885.10..750000347885.29 rows=75 width=566) Sort Key: ((SubPlan 2)) DESC, "user".id DESC -> Bitmap Heap Scan on "user" (cost=8.75..750000347882.77 rows=75 width=566) Recheck Cond: (ext_sys = 1) Filter: (deleted = 0) -> Bitmap Index Scan on idx_user_ext_sys (cost=0.00..8.73 rows=78 width=0) Index Cond: (ext_sys = 1) SubPlan 1 -> Aggregate (cost=19.27..19.28 rows=1 width=8) -> Bitmap Heap Scan on document (cost=4.21..19.25 rows=5 width=4) Recheck Cond: (user_id = "user".ext_id) Filter: ((user_sys = 1) AND (deleted = 0)) -> Bitmap Index Scan on idx_user_id (cost=0.00..4.21 rows=8 width=0) Index Cond: (user_id = "user".ext_id) SubPlan 2 -> Limit (cost=10000004618.93..10000004618.94 rows=1 width=4) -> Sort (cost=10000004618.93..10000005141.43 rows=209000 width=4) Sort Key: user_login_info.created_utc DESC -> Nested Loop (cost=10000000000.15..10000003573.93 rows=209000 width=4) -> Seq Scan on user_login_info (cost=10000000000.00..10000000942.95 rows=1045 width=4) Filter: (user_id = "user".id) -> Materialize (cost=0.15..18.98 rows=200 width=0) -> Index Only Scan using idx_document_user_sys on document document_1 (cost=0.15..17.98 rows=200 width=$ ) Index Cond: (user_sys = 1)
Planning time: 0.212 ms Execution time: 229.674 ms
解决
那么现在首要问题就是如何避免子查询,可以看到需求里是需要最近用户登录时间和用户的项目数,那么一个很自然的思路就是先把这两个数据查出来,然后再和 User Join到一起进行分页即可,这样就可以避免子查询嵌套到父查询里了,这里涉及到一个子查询的优化方法,尽量将关联子查询上推,上推到和父查询一个层级以避免 Nested Loop。
WITH user_projects_query AS (SELECT project.user_id AS user_id, count(project.id) AS cnt FROM project WHERE project.deleted =0GROUPBY document.user_id ), login_utc_query AS (SELECT user_login_info.user_id AS user_id, max(user_login_info.created_utc) AS login_utc FROM user_login_info GROUPBY user_login_info.user_id ) SELECT "user".id AS user_id, "user".username AS username, user_projects_query.cnt AS user_projects_query_cnt, coalesce(login_utc_query.login_utc, "user".created_utc) AS login_utc FROM "user" LEFTOUTERJOIN user_projects_query ON user_projects_query.user_id = "user".id LEFTOUTERJOIN login_utc_query ON login_utc_query.user_id = "user".id WHERE "user".deleted =0 ORDERBY login_utc DESC, "user".ext_id DESC LIMIT 20OFFSET0