在 PostgreSQL 中加快索引创建速度# PG初级

avatar
作者
筋斗云
阅读量:0

到目前为止,索引是每个通用关系数据库提供的性能相关最多的功能。没有索引,就没有高效的搜索操作,没有唯一约束,也没有主键。因此,在没有索引的情况下,在合理数量的数据上实现良好的性能是完全不可能的。

生成样本数据

现在的主要问题是:在索引创建过程中会发生什么,我们如何加快这一过程?在数十亿行上创建索引的成本非常高,因为必须对数据进行排序并将其转换为有效的搜索结构。为了演示如何快速创建索引,我创建了一组简单的演示数据:

blog=# CREATE TABLE t_data AS  SELECT 	id::int4,  (random() * 1000000000)::int4 AS i,  (random() * 1000000000) AS r,  (random() * 1000000000)::numeric AS n  FROM 	generate_series(1, 1000000000) AS id; Time: 1569002.499 ms (26:09.002) 

此示例数据具有几个有趣的属性:“id”列是一个升序数字。在索引创建过程中,这会产生巨大的差异。第二列包含一个随机值乘以行数作为整数值。第三列包含一个“双精度”数字,最后,在最后,我们将类似的数据存储为“数值”,这是一个浮点数,在内部不使用 FPU(CPU 的浮点单元)。

一旦创建了这十亿行,我们就可以使用以下命令轻松检查表的大小:

blog=# SELECT pg_size_pretty(pg_relation_size('t_data'));  pg_size_pretty ----------------  56 GB (1 row) 

一旦创建了测试数据,在PostgreSQL中设置一个名为“提示位”的东西是有意义的,这将使我们能够在各种运行之间进行公平的比较。如果您对提示位及其与 VACUUM 的关系感兴趣,请考虑查看我们关于此主题的博客文章。

PgSQL blog=# VACUUM ANALYZE; VACUUM Time: 91293.971 ms (01:31.294) 

当 VACUUM 运行时,我们可以检查此操作的进度。如果 VACUUM 操作需要很长时间,这可以为我们提供一些有趣的见解,了解您的数据库内部正在发生的事情:

PgSQL demo=# SELECT * FROM pg_stat_progress_vacuum; -[ RECORD 1 ]------+-------------- pid                | 5945 datid              | 1515520 datname            | blog relid              | 1515537 phase              | scanning heap heap_blks_total    | 7352960 heap_blks_scanned  | 212599 heap_blks_vacuumed | 0 index_vacuum_count | 0 max_dead_tuples    | 291 num_dead_tuples    | 0 

创建数据集后,我们可以继续前进,在表上创建简单的索引:

blog=# \d t_data                	Table "public.t_data"  Column |   	Type   	| Collation | Nullable | Default --------+------------------+-----------+----------+---------  id 	   | integer      	|        	 |      	 |  i  	   | integer      	|       	 |      	 |  r  	   | double precision |       	 |      	 |  n   	   | numeric      	|       	 |      	 | 

创建简单索引

以下列表显示了如何为每列编制索引,以及执行此操作所需的时间。请注意,所有这些操作都是使用 PostgreSQL 默认设置进行的,正如我们稍后将看到的那样,这远非理想。所有测试均在“AMD Ryzen Threadripper 2950X 16-Core”处理器上执行:

blog=# CREATE INDEX ON t_data (id); CREATE INDEX Time: 291700.318 ms (04:51.700) 

正如我们所看到的,索引创建需要将近 5 分钟。请记住,我们正在处理 10 亿行,我们甚至还没有开始调整数据库。有趣的是 PostgreSQL 在创建索引时做了什么。我们可以检查系统视图以提供一些线索:

PgSQL blog=# SELECT * FROM pg_stat_progress_create_index; -[ RECORD 1 ]------+------------------------------- pid            	   | 5945 datid          	   | 1515520 datname        	   | blog relid          	   | 1515537 index_relid    	   | 0 command        	   | CREATE INDEX phase          	   | building index: scanning table lockers_total  	   | 0 lockers_done   	   | 0 current_locker_pid | 0 blocks_total   	   | 7352960 blocks_done    	   | 703280 tuples_total   	   | 0 tuples_done    	   | 0 partitions_total   | 0 partitions_done	   | 0 

在第一步中,PostgreSQL将扫描表并为接下来的排序操作准备数据:

PgSQL -[ RECORD 1 ]------+------------------------------------ pid                | 5945 datid              | 1515520 datname            | blog relid              | 1515537 index_relid        | 0 command            | CREATE INDEX phase              | building index: sorting live tuples lockers_total      | 0 lockers_done       | 0 current_locker_pid | 0 blocks_total       | 7352960 blocks_done        | 7352960 tuples_total       | 0 tuples_done        | 0 partitions_total   | 0 partitions_done    | 0 

排序可能非常耗时,因此正确调整此过程以实现良好的性能非常重要。在排序过程之后,PostgreSQL 会将这些排序的元组添加到索引中,如下一列表中所示:

PgSQL -[ RECORD 1 ]------+--------------------------------------- pid            	   | 5945 datid          	   | 1515520 datname        	   | blog relid          	   | 1515537 index_relid    	   | 0 command        	   | CREATE INDEX phase          	   | building index: loading tuples in tree lockers_total  	   | 0 lockers_done   	   | 0 current_locker_pid | 0 blocks_total   	   | 0 blocks_done    	   | 0 tuples_total   	   | 1000000000 tuples_done    	   | 263748386 partitions_total   | 0 partitions_done	   | 0 

创建第一个索引后,我们可以查看第二个索引列。请注意,数据类型和数据量是相同的。尽管如此,我们还是可以观察到,该过程需要更长的时间,因为数据是以随机顺序提供给索引创建的,这在大多数情况下可能会产生巨大的差异:

PgSQL blog=# CREATE INDEX ON t_data (i); CREATE INDEX Time: 401634.592 ms (06:41.635) 

哇,索引创建需要将近 2 分钟的时间,这真的很相关 - 我们谈论的操作比以前要长得多。

然而,数据的物理顺序并不是唯一可以产生巨大差异的东西。对于我们想要索引的数据类型也是如此。只需看一下,看看运行时将如何根据数据类型而变化:

PgSQL blog=# CREATE INDEX ON t_data (r); CREATE INDEX Time: 441994.372 ms (07:21.994) 

“双精度”列再次慢 40 秒。必须记住,“双精度”值也大于整数值,这肯定会造成这些运行时差异。

最后要看的是索引“数字”,它的行为方式与以前完全不同:

PgSQL blog=# CREATE INDEX ON t_data (n); CREATE INDEX Time: 799659.658 ms (13:19.660) 

这里需要注意的是,有两件事真的很重要:

数据是否已排序?
使用哪种数据类型?
这两个方面往往都被完全低估了,因为大多数人只考虑“更多的RAM”和更多的CPU(好像其他什么都不重要)。

尝试许多性能改进

人们经常做的是将硬件和配置参数扔给问题。

通常,以下参数会更改:

PgSQL blog=# ALTER SYSTEM SET max_wal_size TO '100 GB'; ALTER SYSTEM blog=# ALTER SYSTEM SET max_parallel_maintenance_workers TO 10; ALTER SYSTEM blog=# ALTER SYSTEM SET maintenance_work_mem TO '16 GB'; ALTER SYSTEM blog=# ALTER SYSTEM SET shared_buffers TO '64 GB'; ALTER SYSTEM 

完成此操作后,我们可以重新启动数据库。请注意,在不更改的情况下,shared_buffers“SELECT pg_reload_conf()”就完全足够了。

让我们一次一个地讨论这些参数:

  • max_wal_size: 这控制了检查点之间的距离和 WAL 的大小。它对减少 I/O 量和总体上加快 I/O 有很大帮助。
  • max_parallel_maintenance_workers: 此变量控制允许 PostgreSQL
    启动多少个工作进程来构建索引。它定义了工人的上限。
  • maintenance_work_mem: 这定义了每个操作在内存中可以发生多少。
  • shared_buffers:I/O 缓存的大小

让我们再次运行索引创建:

 blog=# CREATE INDEX ON t_data (n); CREATE INDEX Time: 474907.805 ms (07:54.908) 

这很有趣:我们设法将时间从大约 13 分 19 秒减少到近 8 秒。请注意,这甚至不是速度的两倍。但是,CPU怎么说?

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                           8773 hs        20   0   67.8g   2.2g 196256 R  99.0   1.8   1:05.88 postgres                                                                                                          8824 hs        20   0   66.6g 968576 170576 R  99.0   0.7   0:18.96 postgres                                                                                                          8825 hs        20   0   66.6g   1.0g 170796 R  99.0   0.8   0:18.98 postgres                                                                                                          8826 hs        20   0   67.4g   1.8g 170796 R  99.0   1.4   0:18.97 postgres                                                                                                          8827 hs        20   0   67.4g   1.8g 170796 R  99.0   1.4   0:18.95 postgres                                                                                                          8829 hs        20   0   66.6g 990.8m 170796 R  99.0   0.8   0:18.93 postgres                                                                                                          8823 hs        20   0   67.4g   1.8g 170796 R  98.7   1.4   0:18.95 postgres                                                                                                          8828 hs        20   0   66.7g   1.0g 170376 R  98.7   0.8   0:18.95 postgres  

这很有趣:我们在对数据进行排序时抓住了 CPU。现在这是相关的,因为排序阶段可以以一种很好的方式并行完成 - 许多其他阶段不能在RAM中发生,或者不能同时发生。这就是为什么即使有无数的 CPU 内核,我们也只能设法加快速度如此之快的原因。在这里绝对限制我们的是本地SSD的容量,在排序过程中产生大约600 MB / sec。但是,更值得一提的是,使用许多更高的参数设置,对整数变量进行排序的默认配置实际上比完全并行的索引创建更快。

数据类型确实很重要这一事实告诉我们,调整配置参数绝对不是获得启蒙的唯一途径。确定正确的数据类型也会产生巨大的差异。
#PG培训#PG考试#postgresql培训#postgresql考试#postgresql认证

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!