强制PostgreSQL查询优化

当关闭的源应用程序无法以最佳方式访问数据库时该怎么办? 如何在不更改应用程序以及数据库本身的情况下调整查询?

如果您没有问过这样的问题,那么您就是一个非常成功且严格的DBA。

好吧,如果有人问,让我分享痛苦和经验。

需要存储更多数据或设置任务


如果问题的历史记录不有趣,则可以安全地滚动浏览此部分。

最初,我们拥有一个专有系统,该系统将其数据从封闭格式解析为PostgreSQL数据库,然后从中读取,分析和处理该数据。

此外,该系统的工具还将此基础用于某些操作,因此放弃该基础并创建具有其结构的副本似乎是徒劳的想法。

默认情况下,系统会自动删除早于一周的记录,因此在展位上不会出现性能问题。

但是,只要服务器磁盘上有足够的空间,我们就需要存储更长的数据。 好吧,非常建议不要丢失对这些数据的访问,并且仍然使用系统的内置工具,即使对于旧数据也是如此。

因此,显而易见的决定是对INSERT操作进行分区和触发。 重点非常简单有效。 数据被插入到必要的分区中,禁止删除旧记录,一切似乎都很好。

直到几年过去了,数据还没有很好地积累。

在这里,“突然”事实证明,所用系统的工具箱发出的请求并没有按日期限制选择(或者,不将其限制为完成分区的字段)。 即 如果我们正在寻找东西-搜索将在所有分区上进行。 UPDATE操作也开始变慢-在仅使用ID-shnik的情况下。

结果,该请求执行了很长时间,拉低了所有其他请求,负载迅速增长。

当然,首先想到的是与开发人员联系。

但是,在大多数情况下,它要么不再位于访问区域中,要么会要求另一套这样的系统的成本才能在多条线路中完成。

因此,想到可能已经存在某种可以帮助我们的代理。

我们需要一个代理


快速谷歌搜索没有找到关于如何在PostgreSQL或某些第三方软件方面重写传入查询的问题的明确答案。

因此(当然,也只是出于娱乐目的),编写了一个相当简单的软件,该软件接受来自客户端的连接并在PostgreSQL中代理它们。 同时,读取传入的SQL查询,并在必要时将其替换。

共享指向github的链接

虽然我没有制作任何二进制软件包,但我的双手没有伸手。 但是组装非常简单。 一切都用C ++ / Qt编写,因为 我已经为此写了很长时间...

配置非常简单:

指定要监听的接口和端口:

listen_address=0.0.0.0 listen_port=5433 

我们强迫疏忽大意的软件连接到指定的地址,而不是直接连接到PostgreSQL服务器。

我们记下转发连接的位置(在此示例中,代理与PostgreSQL服务器位于同一台机器上):

 dst_address=127.0.0.1 dst_port=5432 

我们设置一个正则表达式来捕获所需的请求:

 query = SELECT \* FROM tablename WHERE (.+) 

我们说我们需要重写它:

 action = rewrite 

我们说如何重写:

 rewrite = SELECT * FROM tablename WHERE (col3 >= '$(now-1M)') AND $(1) 

在此示例中,我们通过带有日期的列为查询条件添加了一个过滤器,表明我们只对上个月的记录感兴趣。

可以这样写:

 rewrite = SELECT * FROM tablename WHERE (col3 >= now() - interval '1 month') AND $(1) 

但是由于now()函数的存在,请求将不是最佳的-搜索仍将在所有分区上执行。 为了仅在必要时进行搜索,必须指定一个常数。 因此,我们的代理服务器将时间戳替换了一个月而不是$(now-1M)构造。

结果(来自日志):

 ORIGINAL query: SELECT * FROM tablename WHERE id=1; MODIFIED query (rule 1): SELECT * FROM tablename WHERE (col3 >= '2018-11-12 11:25:23.0+00') AND id=1; 

因此,原则上可以替换任何请求。 来自服务器的响应不会更改,而是原样发送到客户端。 这样,传输延迟被最小化。 另外,应用程序通常等待某种格式的响应,因此不希望更改请求和响应中的列集。

也可以轻松地将所有感兴趣的请求打印到日志中:

 query = .+ action = log 

该存储库具有一个包含示例和更详细描述的配置。

顺便说一句,很容易确定开发人员正确编写与数据库一起工作的程度。 例如,如果您看到这样一个经常执行的请求,那么该是某人抽烟手册的时候了。

 INSERT INTO tablename (col1, col2, col3) VALUES('value1', 1, '2018-12-31') 

应该是这样的:

 INSERT INTO tablename (col1, col2, col3) VALUES($1::varchar, $2::integer, $3::date) 

不幸的是,到目前为止,我们的代理无法以这种方式编写:/但这并不难做到。 也许将来有可能将第一个请求重写为第二个请求。

是的,重要的一点是尚不支持SSL,因此从客户端到代理的所有连接都将不进行加密。

我将很乐意发表评论。

如果用户有积极的兴趣,也许我会进一步开发该项目。

您可以添加其他数据库的工作。

Source: https://habr.com/ru/post/zh-CN432944/


All Articles