修改一行SQL代码 性能提升了N倍
在PostgreSQL中修改了一行不明显的代码,把(ANY(ARRAY[...]) 改成 ANY(VALUES(...))),结果查询时间从20s变为0.2s。最初我们学习使用EXPLAN ANALYZE来优化代码,到后来,Postgres社区也成为我们学习提升的一个好帮手,付出总会有回报,我们产品的性能也因此得到了极大的提升。
事出有因
我们所开发的产品是Datadog,它是专门为那些编写和运营大规模应用的团队、IT运营商提供监控服务的一个平台,帮助他们把海量的数据转化为切实可行的计划、操作方案。而在这周早些时候,我们的许多数据库所面临的一个性能问题是在一个较小的表上进行大量的key查询。这些查询中的99.9%都是高效灵活的。在极少数实例中,有些数量的性能指标tag查询是费时的,这些查询需要花费20s时间。这也就意味着用户需要在浏览器面前花费这么长的时间来等待图形编辑器做出响应。即使是0.1%,这样的用户体验也显然糟透了,对此,我们进行了监测,探究为何速度会这么慢。
查询与计划
结果令人震惊,罪魁祸首竟然是下面这个简单的查询:
SELECT c.key,
c.x_key,
c.tags,
x.name
FROM context c
JOIN x
ON c.x_key = x.key
WHERE c.key = ANY (ARRAY[15368196, -- 11,000 other keys --)])
AND c.x_key = 1
AND c.tags @> ARRAY[E'blah'];
X表拥有上千行数据,C表拥有1500万行数据,这两个表的“key”列都带有适当的索引主键。简单地说,它就是一个简单的主键查询。但有趣地是,随着key列中记录的增加,例如在11000行时,我们通过添加EXPLAIN (ANALYZE, BUFFERS)前缀来查看key列的值是否与数组中的值匹配托福改分
Nested Loop (cost=6923.33..11770.59 rows=1 width=362) (actual time=17128.188..22109.283 rows=10858 loops=1)
Buffers: shared hit=83494
-> Bitmap Heap Scan on context c (cost=6923.33..11762.31 rows=1 width=329) (actual time=17128.121..22031.783 rows=10858 loops=1)
Recheck Cond: ((tags @> '{blah}'::text[]) AND (x_key = 1))
Filter: (key = ANY ('{15368196,(a lot more keys here)}'::integer[]))
Buffers: shared hit=50919
-> BitmapAnd (cost=6923.33..6923.33 rows=269 width=0) (actual time=132.910..132.910 rows=0 loops=1)
Buffers: shared hit=1342
-> Bitmap Index Scan on context_tags_idx (cost=0.00..1149.61 rows=15891 width=0) (actual time=64.614..64.614 rows=264777 loops=1)
Index Cond: (tags @> '{blah}'::text[])
Buffers: shared hit=401
-> Bitmap Index Scan on context_x_id_source_type_id_idx (cost=0.00..5773.47 rows=268667 width=0) (actual time=54.648..54.648 rows=267659 loops=1)
Index Cond: (x_id = 1)
Buffers: shared hit=941
-> Index Scan using x_pkey on x (cost=0.00..8.27 rows=1 width=37) (actual time=0.003..0.004 rows=1 loops=10858)
Index Cond: (x.key = 1)
Buffers: shared hit=32575
Total runtime: 22117.417 ms
Postgres的性能问题:位图堆扫描
rows_fetched度量与下面的部分计划是一致的:
12345 Buffers: shared hit=83494
-> Bitmap Heap Scan on context c (cost=6923.33..11762.31 rows=1 width=329) (actual time=17128.121..22031.783 rows=10858 loops=1)
Recheck Cond: ((tags @> '{blah}'::text[]) AND (x_key = 1))
Filter: (key = ANY ('{15368196,(a lot more keys here)}'::integer[]))
Buffers: shared hit=50919
Postgres使用位图堆扫描( Bitmap Heap Scan)来读取C表数据。当关键字的数量较少时,它可以在内存中非常高效地使用索引构建位图。如果位图太大,查询优化器会改变其查找数据的方式。在我们这个案例中,需要检查大量的关键字,所以它使用了非常相似的方法来检查候选行并且单独检查与x_key和tag相匹配的每一行。而所有的这些“在内存中加载”和“检查每一行”都需要花费大量的时间。
幸运的是,我们的表有30%都是装载在RAM中,所以在从磁盘上检查行的时候,它不会表现的太糟糕。但在性能上,它仍然存在非常明显的影响。查询过于简单,这是一个非常简单的key查找,所以没有显而易见的数据库或应用重构,它很难找到一些简单的方式来解决这个问题。最后,我们使用 PGSQL-Performance邮件向社区求助托福答案
解决方案
开源帮了我们,经验丰富的且代码贡献量非常多的Tom Lane让我们试试这个:
SELECT c.key,
c.x_key,
c.tags,
x.name
FROM context c
JOIN x
ON c.x_key = x.key
WHERE c.key = ANY (VALUES (15368196), -- 11,000 other keys --)
AND c.x_key = 1
AND c.tags @> ARRAY[E'blah'];
你能发现有啥不同之处吗?把ARRAY换成了VALUES。
我们使用ARRAY[...]列举出所有的关键字来进行查询,但却欺骗了查询优化器。Values(...)让优化器充分使用关键字索引。仅仅是一行代码的改变,并且没有产生任何语义的改变。
下面是新查询语句的写法,差别就在于第三和第十四行。
Nested Loop (cost=168.22..2116.29 rows=148 width=362) (actual time=22.134..256.531 rows=10858 loops=1)
Buffers: shared hit=44967
-> Index Scan using x_pkey on x (cost=0.00..8.27 rows=1 width=37) (actual time=0.071..0.073 rows=1 loops=1)
Index Cond: (id = 1)
Buffers: shared hit=4
-> Nested Loop (cost=168.22..2106.54 rows=148 width=329) (actual time=22.060..242.406 rows=10858 loops=1)
Buffers: shared hit=44963
-> HashAggregate (cost=168.22..170.22 rows=200 width=4) (actual time=21.529..32.820 rows=11215 loops=1)
-> Values Scan on "*VALUES*" (cost=0.00..140.19 rows=11215 width=4) (actual time=0.005..9.527 rows=11215 loops=1)
-> Index Scan using context_pkey on context c (cost=0.00..9.67 rows=1 width=329) (actual time=0.015..0.016 rows=1 loops=11215)
Index Cond: (c.key = "*VALUES*".column1)
Filter: ((c.tags @> '{blah}'::text[]) AND (c.x_id = 1))
Buffers: shared hit=44963
Total runtime: 263.639 ms
从22000ms到200ms,仅仅修改了一行代码,速度提升了100倍还多。
产品里新的查询
部署后的代码:
Postgres慢查询将一去不复返了。但有谁愿意因为这个0.1%的倒霉蛋再去折磨呢?我们使用Datadog来验证修改是否正确,它能够做出即时验证。如果你想查看Postgres查询速度的各种影响, 不妨试试Datadog吧。
修改一行SQL代码 性能提升了N倍的更多相关文章
- 修改一行SQL代码 性能提升了100倍
在PostgreSQL中修改了一行不明显的代码,把(ANY(ARRAY[...]) 改成 ANY(VALUES(...))),结果查询时间从20s变为0.2s.最初我们学习使用 EXPLAN ANAL ...
- Visual Studio Entity Framework (EF) 生成SQL 代码 性能查询
Visual Studio Entity Framework (EF) 生成SQL 代码 性能查询 SQL 中,有SQL Server Profiler可以用来查询性能以及查看外部调用的SQL ...
- 我是如何将一个老系统的kafka消费者服务的性能提升近百倍的
☞☞☞ 我是如何将一个老系统的kafka消费者服务的性能提升近百倍的 ☜☜☜ ○○○○○○○○○○○○○○○ 大家好,又见面了~ kafka作为一种高吞吐量的分布式发布订阅消息系统,在业务系统中被广泛 ...
- Java 中的5个代码性能提升技巧,最高提升近10倍
文章持续更新,可以关注公众号程序猿阿朗或访问未读代码博客. 本文 Github.com/niumoo/JavaNotes 已经收录,欢迎Star. 这篇文章介绍几个 Java 开发中可以进行性能优化的 ...
- 用一个性能提升了666倍的小案例说明在TiDB中正确使用索引的重要性
背景 最近在给一个物流系统做TiDB POC测试,这个系统是基于MySQL开发的,本次投入测试的业务数据大概10个库约900张表,最大单表6千多万行. 这个规模不算大,测试数据以及库表结构是用Dump ...
- 【笔记】直接使用protocol buffers的底层库,对特定场景的PB编解码进行处理,编码性能提升2.4倍,解码性能提升4.8倍
接上一篇文章:[笔记]golang中使用protocol buffers的底层库直接解码二进制数据 最近计划优化prometheus的remote write协议,因为业务需要,实现了一个remote ...
- 优化临时表使用,SQL语句性能提升100倍
[问题现象] 线上mysql数据库爆出一个慢查询,DBA观察发现,查询时服务器IO飙升,IO占用率达到100%, 执行时间长达7s左右.SQL语句如下:SELECT DISTINCT g.*, cp. ...
- 转--优化临时表使用,SQL语句性能提升100倍
转自:http://www.51testing.com/html/01/n-867201-2.html [问题现象] 线上mysql数据库爆出一个慢查询,DBA观察发现,查询时服务器IO飙升,IO占用 ...
- Nacos 2.0 正式发布,性能提升了 10 倍!!
前不久,在3月20号,Nacos 2.0.0 正式发布了!我简单看了下官方的介绍,可能nacos未来逐渐会成为各大公司作为服务治理和配置中心的主要中间件. Nacos 简介:一个更易于构建云原生应用的 ...
随机推荐
- [转]关于Icon,Image,ImageIcon的简单的对比参考
转自:http://blog.csdn.net/bcmissrain/article/details/7190886 其实就算是现在,我还是有不少地方概念模糊,但是下面的内容是是没有什么问题的.稍微介 ...
- 【图片处理】ImageMagick, gm
ImageMagick: Convert, Edit, Or Compose Bitmap Images http://www.imagemagick.org/script/binary-releas ...
- (转载)PHP源代码分析- tick(s)
(转载)http://bbs.phpchina.com/forum.php?mod=viewthread&tid=94534 昨天有位朋友在杭州的PHPer群里面贴出了下面的一段代码并给出了运 ...
- 斜率优化(CDQ分治,Splay平衡树):BZOJ 1492: [NOI2007]货币兑换Cash
Description Input 第一行两个正整数N.S,分别表示小Y 能预知的天数以及初始时拥有的钱数. 接下来N 行,第K 行三个实数AK.BK.RateK,意义如题目中所述 Output 只有 ...
- 控件treeview使用
一:实现功能,获得选中节点,在选中节点下添加节点,折叠,展开,删除,得到选中节点下checked项,选中根节点其下节点也选中,图标.上图 二:相关代码 using System; using Syst ...
- Java---注解、类加载器-加强-实现运行任意目录下class中加了@MyTest的空参方法
做自己的类加载器 虚拟机的核心是通过类加载器来加载.class文件,然后进行相应的解析执行.那么我们可以自己做类加载器,手动加载需要的.class以进行解析执行,从而扩展虚拟机的功能. 以下内容摘自A ...
- 《JavaScript语言精髓与编程实践》读书笔记一
受到狗哥书单的影响,看到了豆瓣上的评论,买了这本书,然后囫囵吞枣似地用一个月的时间看完了.回头想想自己做的js项目,感觉都羞愧-什么东西都是拿来尝试了一下就用了,其实有很多写得超级丑的地方,看完这个让 ...
- Hibernate二 映射 注解 一级缓存
Hibernate映射1.@Entity 被该注解修饰的POJO类是一个实体,可以用name属性指定该实体类的名称,系统默认以该类的类名作为实体类的名称.2.@Table 指定持久化类所映射的表,它的 ...
- linux —— shell 编程(整体框架与基础笔记)
导读 关于shell编程基础的学习,网上有很多资源,如果在校图书馆应该也有一些教程,所以这里对于零碎的基础不做详细记录,而只是对一些常用的概念.命令与操作做一个简要的记录,以备方便查找. (本文所有语 ...
- Android Studio学习随笔-移动动画的实现
在上一篇博客我已经讲述了三种事件的实现方法,而现在我用复用方法来实现控件的自动移动,当然要实现控件的移动,先得在activity_main.xml文件中放置一个控件,此处我放置的是一个button控件 ...