使用PostgreSQL的CloudSQL性能非常缓慢。

5
我希望将数据从BigQuery迁移到CloudSQL以节省成本。 我的问题是,与BigQuery相比,使用PostgreSQL的CloudSQL非常非常慢。 在BigQuery中需要1.5秒的查询,在CloudSQL中使用PostgreSQL则需要将近4.5分钟! 我有一个具有以下配置的CloudSQL PostgreSQL服务器:

enter image description here

我的数据库有一个主表,有1600万行(在内存中大约14GB)。

一个例子查询:

EXPLAIN ANALYZE 
  SELECT 
    "title"
  FROM 
    public.videos
  WHERE 
      EXISTS (SELECT 
                * 
              FROM (
                    SELECT 
                      COUNT(DISTINCT CASE WHEN LOWER(param) LIKE '%thriller%' THEN '0'
                                          WHEN LOWER(param) LIKE '%crime%' THEN '1' END) AS count
                    FROM
                      UNNEST(categories) AS param
                    ) alias
                        WHERE count = 2)

  ORDER BY views DESC 

  LIMIT 12 OFFSET 0

这个表是一个名为videos的表,其中有一个categories列,类型为text[]

这里的搜索条件是查找具有'%thriller%''%crime%'的类别恰好出现两次的记录。

此查询的EXPLAIN ANALYZE输出结果(CSV):link。 此查询的EXPLAIN(BUFFERS)输出结果(CSV):link

查询洞察图:

enter image description here

内存概要:

enter image description here

对于相同的表格大小,BigQuery查询的参考:

enter image description here

服务器配置:链接

表格描述:链接

我的目标是让Cloud SQL具有与Big Query相同的查询速度。


2
请展示表的定义,包括索引。您有没有可能对类别进行规范化? - Jeremy
抱歉,由于IP问题,我无法展示它。没有索引。 - dasdasd
这个回答解决了你的问题吗?https://dev59.com/OVIG5IYBdhLWcg3wozHA#63585216 - RJC
@RJC感谢提供链接。我的数据库不是很大,只有1600万行。我同意BQ适用于真正大的表,但我认为这不是我的情况。 - dasdasd
@jjanes 我会发布更多细节。是的,BQ有执行计划,我可以展示它。但它如何帮助你呢? - dasdasd
显示剩余5条评论
5个回答

1

初始查询看起来过于复杂。可以重新编写为:

SELECT  v."title"
FROM public.videos v
WHERE array_to_string(v.categories, '^') ILIKE ALL (ARRAY['%thriller%', '%crime%'])
ORDER BY views DESC 
LIMIT 12 OFFSET 0;

db<>fiddle演示


感谢您的回答。 我已经在我的数据库上尝试了您的解决方案,但仍然非常非常慢,需要2.5分钟才能完成。 这是EXPLAIN ANALYZE: https://ufile.io/ahk9qoem我希望它能像在大型查询中一样在2秒内运行。 - dasdasd

1

0

除了 SQL 语法优化,你尝试过 Postgresql 调优吗?

我检查了一下解释,发现只有两个并行工作进程和 25K 内存用于排序。

计划工作进程数:2 排序方法:快速排序 内存:25kB

对于你的查询,这是典型的 OLAP 查询。它的性能通常与内存(使用的内存和 CPU 核心数)有关。默认的 Postgres 使用 KB 级别的内存和少量的工作进程。你可以调整 postgresql.conf 以将其优化为 OLAP 类型的数据库。

=================================================== 这是我的建议:使用更多的内存(9MB 作为工作内存)和更多的 CPU(最多 16 个)。

# DB Version: 13
# OS Type: linux
# DB Type: dw
# Total Memory (RAM): 24 GB
# CPUs num: 16
# Data Storage: ssd

max_connections = 40
shared_buffers = 6GB
effective_cache_size = 18GB
maintenance_work_mem = 2GB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 500
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 9830kB
min_wal_size = 4GB
max_wal_size = 16GB
max_worker_processes = 16
max_parallel_workers_per_gather = 8
max_parallel_workers = 16
max_parallel_maintenance_workers = 4

您可以将其添加到postgresql.conf的最后一行。然后重新启动postgresql服务器以使其生效。

为了进一步优化,

  1. 减少连接并增加work_mem。200 * 9830大约是所有连接的2GB内存。如果您有较少的连接(例如100个),则可以获得更多的内存以进行查询工作。

==================================== 关于使用文本数组类型和unnest。您可以尝试添加适当的索引。

就这些了,祝好运。

王勇


1
这个问题与Google Cloud SQL上的PostgreSQL有关 - 没有直接访问postgresql.conf的方式。effective_io_concurrencymax_parallel_workers_per_gathermax_parallel_workersmax_parallel_maintenance_workers这些设置不通过实例定制标志提供。 - Zegarek

0

PostGreSQL在涉及COUNT聚合函数的每个查询中都是按设计缓慢的,除了使用物化视图来强制执行性能外,绝对没有其他办法。

我在我的48核机器上进行的测试(从PostGreSQL到MS SQL Server的COUNT性能比较)清楚地表明:在所有情况下,SQL Server的速度介于61倍至561倍之间,并且使用列存储索引时,SQL Server的速度可以快1,533倍...

使用任何其他RDBMS也可以达到相同的速度。显然,这是由于PG MVCC在表和索引页面中维护幽灵行,需要浏览每一行以确定它是活动行还是幽灵行... 在所有其他RDBMS中,计数是通过仅读取页面顶部的一个信息(页面中的行数)并且使用并行访问或在SQL Server中使用批量访问而不是行访问来完成的...

在PG中加速计数的方法是无可奈何的,直到存储引擎完全重写以避免页面内的幽灵插槽为止...


谢谢你的回答,我尝试了mysql,但它仍然非常非常慢。我使用JSON对象是因为mysql中没有数组类型。 - dasdasd
数组及其相关内容(JSON、XML、记录、多集合等)与关系模型的基本原则相反,这些原则要求数据必须是原子性的。当数据是原子性的时候,就可以通过索引来加速查询。除了像Oracle或Microsoft SQL Server这样的特定数据库可以对XML进行索引之外,其他关系型数据库都不知道如何通过索引来加速访问复合结构,比如数组!顺便说一下,表非常接近于数组,并且可以被索引... - SQLpro

0

我认为您需要使用全文搜索和特殊的GIN索引。步骤如下:

  1. 创建用于索引的辅助函数:CREATE OR REPLACE FUNCTION immutable_array_to_string(text[]) RETURNS text as $$ SELECT array_to_string($1, ','); $$ LANGUAGE sql IMMUTABLE;

  2. 创建索引本身:CREATE INDEX videos_cats_fts_idx ON videos USING gin(to_tsvector('english', LOWER(immutable_array_to_string(categories))));

使用以下查询语句:SELECT title FROM videos WHERE (to_tsvector('english', immutable_array_to_string(categories)) @@ (to_tsquery('english', 'thriller & crime'))) limit 12 offset 0;

请注意,此查询对于“crime”和“thriller”具有不同的含义。它们不仅仅是子字符串,而是英语短语中的标记。但实际上,这对于您的任务来说可能更好。此外,此索引不适用于频繁更改的数据。当您主要拥有只读数据时,它应该能够正常工作。

PS 此答案受到答案和评论的启发:https://dev59.com/S4nda4cB1Zd3GeqPCrGQ#29640418


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接