在PostgreSQL中修改了一行不明显的代码,把(ANY(ARRAY[...]) 改成 ANY(VALUES(...))),结果查询时间从20s变为0.2s。最初我们学习使用EXPLAN ANALYZE来优化代码,到后来,Postgres社区也成为我们学习提升的一个好帮手,付出总会有回报,我们的性能也因此得到了极大的提升。
事出有因
Datadog是专门为那些编写和运营大规模应用的团队、IT运营商提供监控服务的一个平台,通过使用他们的应用程序、工具和服务来把海量的数据转化为切实可行的计划、操作方案。而在这周早些时候,我们的许多数据库所面临的一个性能问题是在一个较小的表上进行大量的key查询。这些查询中的99.9%都是高效灵活的。在极少数实例中,有些数量的性能指标tag查询是费时的,这些查询需要花费20s时间。这也就意味着用户需要在浏览器面前花费这么长的时间来等待图形编辑器做出响应。即使是0.1%,这样的用户体验也显然糟透了,对此,我们进行了监测,探究为何速度会这么慢。
查询与计划
结果令人震惊,罪魁祸首竟然是下面这个简单的查询:
1
2
3
4
5
6
7
8
9
10
|
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列的值是否与数组中的值匹配:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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 |
这次查询共花费22s,我们可以通过下图对这22s进行很直观的了解,其中大部分时间花费在Postgres和OS之间,而磁盘I/O则花费非常少的时间。
在最低水平,这些查询看起来就像是这些CPU利用率的峰值。在这里主要是想证实一个关键点:数据库不会等待磁盘去读取数据,而是做排序、散列和行比较这些事。
通过Postgres获取与峰值最接近的行数。
显然,我们的查询在大多数情况下都有条不紊的执行着。
Postgres的性能问题:位图堆扫描
rows_fetched度量与下面的部分计划是一致的:
1
2
3
4
5
|
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让我们试试这个:
1
2
3
4
5
6
7
8
9
10
|
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(...)让优化器充分使用关键字索引。仅仅是一行代码的改变,并且没有产生任何语义的改变。
下面是新查询语句的写法,差别就在于第三和第十四行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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吧。
来自: Datadog
相关推荐
6. **存储过程**:预编译的T-SQL代码块,可以封装复杂的业务逻辑并重复使用,提高性能和代码的可维护性。 7. **触发器**:在特定的DML(数据修改语言)操作(如INSERT、UPDATE或DELETE)发生时自动执行的代码段,...
本压缩包"一些有用的T-SQL代码"包含了一系列与数据库管理相关的脚本,包括但不限于数据库的创建、表的定义以及约束的设置等。下面将详细阐述这些知识点: 1. **数据库的创建**: 在T-SQL中,我们可以使用`CREATE ...
有INNER JOIN(仅返回匹配的行),LEFT JOIN(返回左表所有行,右表匹配的行),RIGHT JOIN(返回右表所有行,左表匹配的行),和FULL OUTER JOIN(返回两表的所有行,不匹配的行用NULL填充)。 4. **子查询**:...
在本文中,我们将深入探讨如何通过修改一行代码显著提升 PostgreSQL 数据库的性能,这个案例中性能提升了大约100倍。问题出现在一个特定的查询中,它在执行时耗费了20秒的时间,但通过一个微小的改动,查询时间被...
SQL Server性能优化是数据库管理员和开发人员必须掌握的重要技能,它对于确保数据库系统的稳定性和响应速度至关重要。在生产环境中,正确的优化方法可以显著提升数据库的处理能力和效率,减少资源消耗,并避免潜在的...
在SQL的世界里,掌握高级代码和全面的文档是提升数据库管理与数据分析能力的关键。"SQL出高级代码、文档大全"这个资源集成了作者在培训期间的学习成果,旨在为学习者提供一套系统的SQL学习资料,帮助大家提升技能并...
在IT领域,SQL(Structured Query Language)是用于管理和操作关系数据库的强大...通过深入理解和实践这些SQL与关系数据库理论,开发者可以编写出更加健壮、高效的SQL代码,从而优化数据库系统的性能,满足业务需求。
例如,它可以确保每个FROM关键字后面都有一个新行,每个JOIN操作都独立一行,使复杂的联接查询更容易理解。 此外,这样的软件通常还会包含其他辅助功能,如错误检查、性能分析、代码重构等。错误检查可以在编写过程...
在SQL数据库领域,掌握高级应用是提升数据库管理效率和数据安全性的关键。本资源包主要涵盖了几个核心主题:视图、游标、存储过程和触发器,这些都是SQL中用于复杂数据处理和业务逻辑的重要工具。 首先,让我们深入...
在这个名为"SQL上机部分"的压缩包中,我们很可能是找到了一系列与北大青鸟教育机构的SQL教学相关的代码实例或练习题。北大青鸟是中国知名的IT职业教育机构,其课程覆盖了广泛的编程和技术领域,包括SQL。 SQL的基础...
《SQL Server 2000案例教程代码》是针对数据库管理系统SQL Server 2000的一份实践性学习资料,其中包含了多种系统原码,旨在帮助用户深入理解SQL Server 2000的工作原理和应用技巧。这个压缩包中的文件可能是以实际...
3. 触发器与存储过程:触发器是一种在特定数据库事件发生时自动执行的SQL代码,常用于实现业务规则或数据完整性。存储过程则是预编译的SQL语句集合,可以提高性能并简化调用。这两个特性在实际开发中广泛使用。 4. ...
存储过程是预编译的T-SQL代码集合,可以重复使用,提高效率并封装业务逻辑。 6. 触发器和游标:触发器是响应数据更改自动执行的特殊存储过程,常用于实现复杂的业务规则。游标则提供了一种逐行处理数据的能力,虽然...
- **触发器**:自动执行的SQL代码,响应特定的数据修改事件。 - **事务管理**:确保数据一致性,支持ACID属性(原子性、一致性、隔离性和持久性)。 在实际项目中,还需要考虑性能优化、安全性和事务处理等方面。...
《Microsoft SQL Server 2008技术内幕:T-SQL查询》是一本深入探讨SQL Server 2008中T-SQL(Transact-SQL)查询的权威指南。该书涵盖了从基本概念到高级特性的全方位知识,旨在帮助读者理解和掌握SQL Server 2008的...
4. T-SQL性能问题与优化:性能问题通常是由于编写不当的T-SQL代码导致的,存储过程调试中需要识别和优化性能不佳的查询。 5. SQLServer特定调试技术: - 使用游标进行行处理 - 存储过程与嵌套查询优化 - SQL...
SQL编码规范是为了确保SQL代码的质量、可读性和可维护性而制定的一套标准。良好的编码规范能够帮助团队成员更容易地理解和修改现有代码,同时也能够提升代码的整体质量。 #### 二、排版规则 **1.2 一般规则** - *...
SQL(Structured Query Language)是用于管理和处理关系数据库的标准语言,而PL/SQL(Procedural Language/Structured Query Language)则是Oracle公司开发的一种过程化编程语言,它扩展了SQL的功能,使得在数据库中...
`用于清空之前添加的SQL语句,确保每次循环都只插入一行数据。 3. **构建SQL语句**:`sql.Add('insert into customer_info values(:b1)');`构建插入语句,使用参数化查询避免SQL注入的风险。 4. **设置参数值**:`...