原始记录一次性能优化过程

    起因是协同中有个界面,当数据量达到5w以上的时候,载入速度比较慢。

    当时想这个问题很奇怪啊,才5W数据,怎么会造成性能问题呢。经过抓取sql看执行时间,得到其中一个sql确实很慢,达到了12837.277 ms。这是不可忍耐的:

SELECT DISTINCT
	A .ID,A .Name,A .a,A .b,A .c,A .d,A .e,A .f,A .g,A .h,A .i,A .j,A .kid,A .l
FROM
	 A,
	 C
WHERE
A. STATE = 0
AND A.d = -6546434384760714573
AND A.ID = C.aId
AND (
	C.mId IN (-5162051908097979680, -4684993734047936633, -4188876460025819562, -2445024670931664111, -2127697530593523428, -1730833917365171641, 1640843220652215198, 3967511645675736983, 4078688053696283097, 4585915779813822190, 6053039406954661992, 8708984262145166406, 8775698007915447638, 9009299994434554372)
	OR A.kId = 4585915779813822190
)
ORDER BY
	A .top_Sequence DESC,
	A .issue_Time DESC

LIMIT 6

    其中A表的id是主键,在state ,d,kId 字段以及top_Sequence和issue_Time 字段都有索引;C表在MId和aId也有索引。C表数据量是A表的N倍(N>5)。

    在postgre中执行计划如下:

Limit  (cost=220071.59..220073.04 rows=6 width=79) (actual time=12806.229..12806.441 rows=6 loops=1)
  ->  Unique  (cost=220071.59..238499.62 rows=76317 width=79) (actual time=12806.228..12806.439 rows=6 loops=1)
        ->  Sort  (cost=220071.59..221300.13 rows=491414 width=79) (actual time=12806.226..12806.345 rows=161 loops=1)
              Sort Key: a.top_sequence, A .ID,A .Name,A .a,A .b,A .c,A .d,A .e,A .f,A .g,A .h,A .i,A .j,A .kid,A .l
              Sort Method: external merge  Disk: 107552kB
              ->  Hash Join  (cost=9117.45..129943.80 rows=491414 width=79) (actual time=131.803..2268.255 rows=1116474 loops=1)
                    Hash Cond: (c.aid = a.id)
                    Join Filter: ((c.mid = ANY ('{-5162051908097979680,-4684993734047936633,-4188876460025819562,-2445024670931664111,-2127697530593523428,-1730833917365171641,1640843220652215198,3967511645675736983,4078688053696283097,4585915779813822190,6053039406954661992,8708984262145166406,8775698007915447638,9009299994434554372}'::bigint[])) OR (a.kid = 4585915779813822190::bigint))
                    ->  Seq Scan on c  (cost=0.00..42803.06 rows=2205306 width=16) (actual time=0.036..422.028 rows=2205306 loops=1)
                    ->  Hash  (cost=7194.48..7194.48 rows=76317 width=79) (actual time=80.471..80.471 rows=76723 loops=1)
                          Buckets: 1024  Batches: 16 (originally 8)  Memory Usage: 1025kB
                          ->  Bitmap Heap Scan on a  (cost=1435.73..7194.48 rows=76317 width=79) (actual time=5.576..36.533 rows=76723 loops=1)
                                Recheck Cond: (A.d = (-6546434384760714573)::bigint)
                                Filter: (state = 0)
                                ->  Bitmap Index Scan on idx_d  (cost=0.00..1416.65 rows=76317 width=0) (actual time=5.350..5.350 rows=76723 loops=1)
                                      Index Cond: (A.d = (-6546434384760714573)::bigint)
Total runtime: 12837.277 ms

    可见在最上面的sort之前实际用时2268.255ms,在Filter之前用时422.028ms 这两项比较耗时。

    考察数据库中的实际数据后,看到a表中state的状态很少,而且state=0的数据大于总数据量的90%,还有A.d字段不同值也不多(测试时少于100)。这两个字段的索引基本没有起作用。先去掉试试。

    再次执行后,速度提升2000ms左右。总耗时约10000ms。

    再次看执行计划,可以看到基本都是sort在耗时。引起sort的是order by 和distinct 。但order by 不能去掉,因为界面要取排序后的数据展示。而distinct可以通过改写sql规避。改写后的sql:

SELECT 
	A .ID,A .Name,A .a,A .b,A .c,A .d,A .e,A .f,A .g,A .h,A .i,A .j,A .kid,A .l
FROM
	A
WHERE
	A. STATE = 0
AND A.d = -6546434384760714573
AND A.ID in(select C.aId from C  where  C.aId = A.ID 
	     AND (
                   C.mId IN (-5162051908097979680, -4684993734047936633, -4188876460025819562, -2445024670931664111, -2127697530593523428, -1730833917365171641, 1640843220652215198, 3967511645675736983, 4078688053696283097, 4585915779813822190, 6053039406954661992, 8708984262145166406, 8775698007915447638, 9009299994434554372)
			OR A.kId = 4585915779813822190
		)
	)
ORDER BY
	A .top_Sequence DESC,
	A .issue_Time DESC

LIMIT 6

执行计划如下:

Limit  (cost=2664060.25..2664060.26 rows=6 width=79) (actual time=1066.189..1066.190 rows=6 loops=1)
  ->  Sort  (cost=2664060.25..2664155.65 rows=38159 width=79) (actual time=1066.187..1066.187 rows=6 loops=1)
        Sort Key: a.top_sequence, a.issue_time
        Sort Method: top-N heapsort  Memory: 17kB
        ->  Index Scan using idx_bbsart_fh on a  (cost=0.00..2663376.26 rows=38159 width=79) (actual time=7.383..987.514 rows=76703 loops=1)
              Index Cond: ((A.d = (-6546434384760714573)::bigint) AND (state = 0))
              Filter: (SubPlan 1)
              SubPlan 1
                -> Index Scan using Idx_aid on c (cost=0.00..69.16 rows=3 width=8) (actual time=0.011..0.011 rows=1 loops=76723)
                      Index Cond: (c.aid = a.id)
                      Filter: ((mid = ANY ('{-5162051908097979680,-4684993734047936633,-4188876460025819562,-2445024670931664111,-2127697530593523428,-1730833917365171641,1640843220652215198,3967511645675736983,4078688053696283097,4585915779813822190,6053039406954661992,8708984262145166406,8775698007915447638,9009299994434554372}'::bigint[])) OR (a.issue_user_id = 4585915779813822190::bigint))
Total runtime: 1066.264 ms

    总用时为1066.264ms。没有了external merge 这个最大的耗用。这时增加对order by中两个字段的组合索引也没太大效果。

    也许觉得这样的速度也算可以了,毕竟还有个order by在哪儿摆着,不可能太快的吧。

    错!还可以更加快!

    因为后面几个in都是前台传的数据,不能更改为其他的。而且,看计划主要性能消耗是在7W多数据的loop项,而且执行计划中的“Sort Method: top-N heapsort  Memory: 17kB”是耗性能的大户。但是每次loop只消耗0.011ms,因此如何使这个loop次数减少是关键。因为我们只需要前6项,其他数据可以不需要loop出的。

    想起最开始的时候把state和d 两个单独字段的索引去掉了,是否可以利用下这两个字段呢,因此又加上了,不怎么见效果,反而慢了。

    再看order by,这两字段都是有索引的,但是没走,岂不是浪费,因为查出的数据量太大吗?NO,因为利用不充分!

    建立组合索引(state, d, top_sequence, issue_time)。在看执行计划,其他没变,但是loop成6次了(其他不贴了):

  (cost=0.00..69.16 rows=3 width=8) (actual time=0.006..0.006 rows=1 loops=6)
  Total runtime: 0.105 ms

    神一样的速度啊。这才对嘛!

    大多数程序员在写hql的时候都是实现功能就好了,而这种limit操作使得这种不当的操作更加的隐蔽(大点的数据量或者数据量不太大但数据分配不均匀才能发现问题)。而这里的order by更加剧了问题的凸显。这是好事,否则到后期再发现恐怕造成的后果更加严重。

    造成最初查询很慢的原因是结果没用上应该用的索引,直接对order by 两个字段建组合索引没效果的原因是没使结果集变小。最终优化结果只需0.1ms是因为:首先是走了索引,其次是结果集变小了,因为有了索引,仅需扫描满足条件的6行就可以得到结果(实际扫描行数要比6多一些,看表中数据分布而定)。

    总结就是:尽量让查询使用索引,多表连接用尽量INNER JOIN,避免使用DISTINCT,灵活使用相关子查询(上面的最优查询如果没有重复的记录也可以用inner join代替),尽量少做重复性的查询工作。

    END


 

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值