回答

收藏

生产中的PostgreSQL查询速度慢-帮助我理解此解释分析输出

技术问答 技术问答 186 人阅读 | 0 人回复 | 2023-09-14

我有一个查询,需要9分钟GCC gcc(GCC)4.1.2 20080704(Red Hat
: K% V3 u" e- v+ C" ^4.1.2-46),64位编译人员x86_64-unknown-linux-gnu上的PostgreSQL 9.0.0上运行) n6 i0 m7 Z7 O7 w3 F& D* y
这个查询是由hibernate自动为我的应用程序生成。它试图在一所学校找到所有的教师成员。成员资格是在小组中扮演角色的用户。有几种类型的团队,但在这里重要的是学校和服务。如果有人既是服务教师,也是学校(15499),那么他们就是我们需要的。9 D' P2 J+ ~& i+ s9 b
查询过去在生产环境中运行良好,但在开发环境中仍然运行良好,但在生产环境中运行需要几分钟。你能帮我理解为什么吗?
9 w8 f* ?$ G0 u/ x这是查询:1 }! p! T4 f, w: z
select distinct user1_.ID as ID14_,user1_.FIRST_NAME as FIRST2_14_,user1_.LAST_NAME as LAST3_14_,user1_.STREET_1 as STREET4_14_,user1_.STREET_2 as STREET5_14_,user1_.CITY as CITY14_,user1_.us_state_id as us7_14_,user1_.REGION as REGION14_,user1_.country_id as country9_14_,user1_.postal_code as postal10_14_,user1_.USER_NAME as USER11_14_,user1_.PASSWORD as PASSWORD14_,user1_.PROFESSION as PROFESSION14_,user1_.PHONE as PHONE14_,user1_.URL as URL14_,user1_.bio as bio14_,user1_.LAST_LOGIN as LAST17_14_,user1_.STATUS as STATUS14_,user1_.birthdate as birthdate14_,user1_.ageInYears as ageInYears14_,user1_.deleted as deleted14_,user1_.CREATEDATE as CREATEDATE14_,user1_.audit as audit14_,user1_.migrated2008 as migrated24_14_,user1_.creator as creator14_ from DIR_MEMBERSHIPS membership0_ inner join DIR_USERS user1_ on membership0_.USER_ID=user1_.ID,DIR_ROLES role2_,DIR_GROUPS group4_ where membership0_.role=role2_.ID and membership0_.GROUP_ID=group4_.id and membership0_.GROUP_ID=15499 and case when membership0_.expires is null     then 1     else case when (membership0_.expires > CURRENT_TIMESTAMP and (membership0_.startDate is null or membership0_.startDate  CURRENT_TIMESTAMP                         and (membership7_.startDate is null or membership7_.startDate 解释分析输出:
; _" ]' E& }  e8 i: X    HashAggregate  (cost=61755.63..61755.64 rows=1 width=3334) (actual time=652504.302..652504.307 rows=4 loops=1)   ->  Nested Loop  (cost=4355.35..61755.56 rows=1 width=3334) (actual time=304.450..652504.217 rows=6 loops=1)      ->  Nested Loop  (cost=4355.35..61747.28 rows=1 width=3342) (actual time=304.419..652504.060 rows=6 loops=1)            ->  Nested Loop Semi Join  (cost=4355.35..61738.97 rows=1 width=32) (actual time=304.385..652503.961 rows=6 loops=1)                  Join Filter: (user_id = user_id)                 ->  Nested Loop  (cost=0.00..32.75 rows=1 width=16) (actual time=0.190..26.703 rows=758 loops=1)                        ->  Seq Scan on dir_roles role2_  (cost=0.00..1.25 rows=1 width=8) (actual time=0.032..0.038 rows=1 loops=1)                              Filter: ((NOT deleted) AND ((name)::text = 'ROLE_MEMBER'::text))                       ->  Index Scan using dir_memberships_role_group_id_index on dir_memberships membership0_  (cost=0.00..31.49 rows=1 width=24) (actual time=0.151..25.626 rows=758 loops=1)                              Index Cond: ((role = role2_.id) AND (group_id = 1549))                                   Filter: ((NOT deleted) AND (CASE WHEN (expires IS NULL) THEN 1 ELSE CASE WHEN ((expires > now()) AND ((startdate IS NULL) OR (startdate   Nested Loop  (cost=4355.35..61692.86 rows=1069 width=16) (actual time=91.088..843.967 rows=79986 loops=758)                ->  Nested Loop  (cost=4355.35..54185.33 rows=1069 width=8) (actual time=91.065..555.830 rows=79986 loops=758)                      ->  Seq Scan on dir_roles role9_  (cost=0.00..1.25 rows=1 width=8) (actual time=0.006..0.013 rows=1 loops=758)                            Filter: ((name)::text = 'ROLE_TEACHER_MEMBER'::text)                             ->  Bitmap Heap Scan on dir_memberships membership7_  (cost=4355.35..53983.63 rows=16036 width=16) (actual time=91.047..534.236 rows=79986 loops=758)                            Recheck Cond: (role = role9_.id)                                   Filter: ((NOT deleted) AND (CASE WHEN (expires IS NULL) THEN 1 ELSE CASE WHEN ((expires > now()) AND ((startdate IS NULL) OR (startdate   Bitmap Index Scan on dir_memberships_role_index  (cost=0.00..4355.09 rows=214190 width=0) (actual time=87.050..87.050 rows=375858 loops=758)                                  Index Cond: (role = role9_.id)                       ->  Index Scan using dir_users_pkey on dir_users user8_  (cost=0.00..7.01 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=60629638)                                     Index Cond: (id = user_id)           ->  Index Scan using dir_users_pkey on dir_users user1_  (cost=0.00..8.29 rows=1 width=3334) (actual time=0.011..0.011 rows=1 loops=6)               Index Cond: (id = user_id)                 Filter: ((NOT deleted) AND ((status)::text = 'active'::text))     ->  Index Scan using dir_groups_pkey on dir_groups group4_  (cost=0.00..8.28 rows=1 width=8) (actual time=0.023..0.023 rows=1 loops=6)           Index Cond: (group4_.id =       Filter: (NOT group4_.deleted)Total runtime: 652504.827 ms(29 rows)我正在阅读论坛帖子和用户手册,但除非有可能使用论坛帖子和用户手册,否则我想不出什么能让它运行得更快。now()功能选择创建索引。
0 S. ?1 C! M) x* G  b4 ^: u# R                                                                ) u% a; e6 `$ @3 Z8 g% k7 R) J
    解决方案:                                                                - Q2 C$ h# M5 K& C7 w/ d: v2 S
                                                                我重写了你的查询,以为会更快:, F2 m; i: R0 @  T
SELECT u.id AS id14_,u.first_name AS first2_14_,u.last_name AS last3_14_,u.street_1 AS street4_14_,u.street_2 AS street5_14_,u.city AS city14_,u.us_state_id AS us7_14_,u.region AS region14_,u.country_id AS country9_14_,u.postal_code AS postal10_14_,u.user_name AS user11_14_,u.password AS password14_,u.profession AS profession14_,u.phone AS phone14_,u.url AS url14_,u.bio AS bio14_,u.last_login AS last17_14_,u.status AS status14_,u.birthdate AS birthdate14_,u.ageinyears AS ageinyears14_,u.deleted AS deleted14_,u.createdate AS createdate14_,u.audit AS audit14_,u.migrated2008 AS migrated24_14_,u.creator AS creator14_FROM   dir_users u WHERE  u.status = 'active'AND    u.deleted = FALSEAND    EXISTS (   SELECT 1   FROM   dir_memberships m   JOIN   dir_roles       r ON r.id = m.role   JOIN   dir_groups      g ON g.id = m.group_id   WHERE  m.group_id = 15499   AND    m.user_id = u.id   AND   (m.expires IS NULL       OR m.expires > now() AND (m.startdate IS NULL OR m.startdate  now() AND (m.startDate IS NULL OR m.startDate 用重写 EXISTScase ... end = 1用简单的表达式取代了奇怪的表达式8 K( o6 ]* ~) N6 @3 b
重写所有显式连接语法JOIN,让它更容易阅读。- a" o  V  R/ ?
将大JOIN结构和IN两种表达式转换EXISTS半联接,这使得有必要坦白DISTINCT。这应该快得多。) ~% b+ ^# f5 |' e+ {% _
许多小的修改使查询更容易,但它们并没有改变内容。
: n0 N8 C" ^+ ~尤其要使用simper别名-你的噪音和混乱。
指标如果这还不够快,你的写入性能可以处理更多的索引,请添加这部分多列索引:( v" l' ^  j3 {! h% N5 ^; |8 |
CREATE INDEX dir_memberships_g_id_u_id_idx ON dir_memberships (group_id,user_id)WHERE  deleted = FALSE;该WHERE符合您查询索引的条件是有用的!8 l. }) t! U) W- G' N
假设你已经在主键和相关外键上有索引了。; v" D. L' E- g' P* |6 S
进一步:
7 [, c- F: y3 pCREATE INDEX dir_memberships_u_id_role_idx ON dir_memberships (user_id,role)WHERE  deleted = FALSE;为什么user_id第二次?. [$ z, _6 _: {  M0 r+ r' X
PostgreSQL索引工作
. ^' Z' S: ]  s: W. k( K7 X. N查询第一个字段的复合索引也有用吗?
另外,由于user_id已经在另一个索引中使用,因此您不会阻止HOT更新(只能与不涉及任何索引的列一起使用)。
7 _2 i6 b! L: Y* R% U# E" W为什么role呢?
! J. D( h. Q6 G% L7 h( O我假设这两列都是类型integer(4个字节)。我已经在你的详细问题上看到了,你在操作MAXALIGN8位字节的64位操作系统,所以另一个整数根本不会增加索引。role认为是第二个EXISTS半连接可能有用。
8 H6 U2 S% D$ y; G- D- f: M0 _如果你有很多死亡用户,也可能有帮助:  \! w8 ~, j2 ~6 O" g
CREATE INDEX dir_users_id_idx ON dir_users (id)WHERE status = 'active' AND deleted = FALSE;和往常一样,检查EXPLAIN检查索引是否真的被使用。您不希望浪费资源的无用索引。
: T. w8 \' L% |+ a我们斋戒了吗?2 A  ~# |, }! p3 \. V0 q
当然,所有关于性能优化的常规建议也适用。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则