优化 Postgres 删除孤立记录

2023-11-25

取下面两张表:

Table "public.contacts"
       Column       |            Type             |                       Modifiers                       | Storage  | Stats target | Description
--------------------+-----------------------------+-------------------------------------------------------+----------+--------------+-------------
 id                 | integer                     | not null default nextval('contacts_id_seq'::regclass) | plain    |              |
 created_at         | timestamp without time zone | not null                                              | plain    |              |
 updated_at         | timestamp without time zone | not null                                              | plain    |              |
 external_id        | integer                     |                                                       | plain    |              |
 email_address      | character varying           |                                                       | extended |              |
 first_name         | character varying           |                                                       | extended |              |
 last_name          | character varying           |                                                       | extended |              |
 company            | character varying           |                                                       | extended |              |
 industry           | character varying           |                                                       | extended |              |
 country            | character varying           |                                                       | extended |              |
 region             | character varying           |                                                       | extended |              |
 ext_instance_id    | integer                     |                                                       | plain    |              |
 title              | character varying           |                                                       | extended |              |
Indexes:
    "contacts_pkey" PRIMARY KEY, btree (id)
    "index_contacts_on_ext_instance_id_and_external_id" UNIQUE, btree (ext_instance_id, external_id)

and

Table "public.members"
        Column         |            Type             |                             Modifiers                              | Storage  | Stats target | Description
-----------------------+-----------------------------+--------------------------------------------------------------------+----------+--------------+-------------
 id                    | integer                     | not null default nextval('members_id_seq'::regclass)               | plain    |              |
 step_id               | integer                     |                                                                    | plain    |              |
 contact_id            | integer                     |                                                                    | plain    |              |
 rule_id               | integer                     |                                                                    | plain    |              |
 request_id            | integer                     |                                                                    | plain    |              |
 sync_id               | integer                     |                                                                    | plain    |              |
 status                | integer                     | not null default 0                                                 | plain    |              |
 matched_targeted_rule | boolean                     | default false                                                      | plain    |              |
 external_fields       | jsonb                       |                                                                    | extended |              |
 imported_at           | timestamp without time zone |                                                                    | plain    |              |
 campaign_id           | integer                     |                                                                    | plain    |              |
 ext_instance_id       | integer                     |                                                                    | plain    |              |
 created_at            | timestamp without time zone |                                                                    | plain    |              |
Indexes:
    "members_pkey" PRIMARY KEY, btree (id)
    "index_members_on_contact_id_and_step_id" UNIQUE, btree (contact_id, step_id)
    "index_members_on_campaign_id" btree (campaign_id)
    "index_members_on_step_id" btree (step_id)
    "index_members_on_sync_id" btree (sync_id)
    "index_members_on_request_id" btree (request_id)
    "index_members_on_status" btree (status)

主键和索引都存在members.contact_id.

我需要删除任何contact其中没有相关的members。大约有3MMcontact和25MMmember记录。

我正在尝试以下两个查询:

Query 1:

DELETE FROM "contacts"
WHERE  "contacts"."id" IN (SELECT "contacts"."id" 
                           FROM   "contacts" 
                                  LEFT OUTER JOIN members 
                                               ON 
                                  members.contact_id = contacts.id 
                           WHERE  members.id IS NULL);

DELETE 0
Time: 173033.801 ms

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Delete on contacts  (cost=2654306.79..2654307.86 rows=1 width=18) (actual time=188717.354..188717.354 rows=0 loops=1)
   ->  Nested Loop  (cost=2654306.79..2654307.86 rows=1 width=18) (actual time=188717.351..188717.351 rows=0 loops=1)
         ->  HashAggregate  (cost=2654306.36..2654306.37 rows=1 width=16) (actual time=188717.349..188717.349 rows=0 loops=1)
               Group Key: contacts_1.id
               ->  Hash Right Join  (cost=161177.46..2654306.36 rows=1 width=16) (actual time=188717.345..188717.345 rows=0 loops=1)
                     Hash Cond: (members.contact_id = contacts_1.id)
                     Filter: (members.id IS NULL)
                     Rows Removed by Filter: 26725870
                     ->  Seq Scan on members  (cost=0.00..1818698.96 rows=25322396 width=14) (actual time=0.043..160226.686 rows=26725870 loops=1)
                     ->  Hash  (cost=105460.65..105460.65 rows=3205265 width=10) (actual time=1962.612..1962.612 rows=3196180 loops=1)
                           Buckets: 262144  Batches: 4  Memory Usage: 34361kB
                           ->  Seq Scan on contacts contacts_1  (cost=0.00..105460.65 rows=3205265 width=10) (actual time=0.011..950.657 rows=3196180 loops=1)
         ->  Index Scan using contacts_pkey on contacts  (cost=0.43..1.48 rows=1 width=10) (never executed)
               Index Cond: (id = contacts_1.id)
 Planning time: 0.488 ms
 Execution time: 188718.862 ms

Query 2:

DELETE FROM contacts 
WHERE  NOT EXISTS (SELECT 1 
                   FROM   members c 
                   WHERE  c.contact_id = contacts.id); 

DELETE 0
Time: 170871.219 ms

-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Delete on contacts  (cost=2258873.91..2954594.50 rows=1895601 width=12) (actual time=177523.034..177523.034 rows=0 loops=1)
   ->  Hash Anti Join  (cost=2258873.91..2954594.50 rows=1895601 width=12) (actual time=177523.029..177523.029 rows=0 loops=1)
         Hash Cond: (contacts.id = c.contact_id)
         ->  Seq Scan on contacts  (cost=0.00..105460.65 rows=3205265 width=10) (actual time=0.018..1068.357 rows=3196180 loops=1)
         ->  Hash  (cost=1818698.96..1818698.96 rows=25322396 width=10) (actual time=169587.802..169587.802 rows=26725870 loops=1)
               Buckets: 262144  Batches: 32  Memory Usage: 36228kB
               ->  Seq Scan on members c  (cost=0.00..1818698.96 rows=25322396 width=10) (actual time=0.052..160081.880 rows=26725870 loops=1)
 Planning time: 0.901 ms
 Execution time: 177524.526 ms

正如您所看到的,即使不删除任何记录,两个查询也显示出相似的性能,大约需要 3 分钟。

服务器磁盘 I/O 峰值达到 100%,因此我假设数据正在溢出到磁盘,因为在两个磁盘上都进行了顺序扫描contacts and members.

服务器是 EC2 r3.large(15GB RAM)。

关于我可以做什么来优化这个查询有什么想法吗?

更新#1:

运行后vacuum analyze对于两个表并确保enable_mergejoin被设定为on查询时间没有区别:

DELETE FROM contacts 
WHERE  NOT EXISTS (SELECT 1 
                   FROM   members c 
                   WHERE  c.contact_id = contacts.id); 

-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Delete on contacts  (cost=2246088.17..2966677.08 rows=1875003 width=12) (actual time=209406.342..209406.342 rows=0 loops=1)
   ->  Hash Anti Join  (cost=2246088.17..2966677.08 rows=1875003 width=12) (actual time=209406.338..209406.338 rows=0 loops=1)
         Hash Cond: (contacts.id = c.contact_id)
         ->  Seq Scan on contacts  (cost=0.00..105683.28 rows=3227528 width=10) (actual time=0.008..1010.643 rows=3227462 loops=1)
         ->  Hash  (cost=1814029.74..1814029.74 rows=24855474 width=10) (actual time=198054.302..198054.302 rows=27307060 loops=1)
               Buckets: 262144  Batches: 32  Memory Usage: 37006kB
               ->  Seq Scan on members c  (cost=0.00..1814029.74 rows=24855474 width=10) (actual time=1.132..188654.555 rows=27307060 loops=1)
 Planning time: 0.328 ms
 Execution time: 209408.040 ms

更新2:

PG版本:

PostgreSQL 9.4.4 on x86_64-pc-linux-gnu, compiled by x86_64-pc-linux-gnu-gcc (Gentoo Hardened 4.5.4 p1.0, pie-0.4.7) 4.5.4, 64-bit

关系尺寸:

         Table         |  Size   | External Size
-----------------------+---------+---------------
 members               | 23 GB   | 11 GB
 contacts              | 944 MB  | 371 MB

设置:

 work_mem
----------
 64MB

 random_page_cost
------------------
 4

更新3:

尝试批量执行此操作似乎对 I/O 使用率没有帮助(仍然飙升至 100%),并且尽管使用了基于索引的计划,但似乎也没有按时改进。

DO $do$ 
BEGIN 
  FOR i IN 57..668 
  LOOP 
    DELETE 
    FROM   contacts 
    WHERE  contacts.id IN 
           ( 
                           SELECT          contacts.id 
                           FROM            contacts 
                           left outer join members 
                           ON              members.contact_id = contacts.id 
                           WHERE           members.id IS NULL 
                           AND             contacts.id >= (i    * 10000) 
                           AND             contacts.id < ((i+1) * 10000));
END LOOP;END $do$;

我不得不在之后终止查询Time: 1203492.326 ms在查询运行的整个过程中,磁盘 I/O 保持在 100%。我还尝试了 1,000 和 5,000 个块,但没有看到性能有任何提高。

注:57..668使用范围是因为我知道这些是现有的联系人 ID。 (例如。min(id) and max(id))


关于我可以做什么来优化这个查询有什么想法吗?

你的查询很完美。我会用NOT EXISTS变体。
您的索引index_members_on_contact_id_and_step_id也有好处:

  • 复合索引也适合第一个字段的查询吗?

但请参阅下文有关 BRIN 指数的内容。

您可以调整服务器、表和索引配置。

由于您几乎不更新或删除任何行,根据您的评论,重点关注优化read表现。

1.升级你的Postgres版本

您提供:

服务器是 EC2 r3.large(15GB RAM)。

And:

PostgreSQL 9.4.4

你的版本已经严重过时了。At least升级到最新的小版本。更好的是,升级到当前的主要版本。 Postgres 9.5 和 9.6 为大数据带来了重大改进——这正是您所需要的。

考虑项目的版本控制策略。

亚马逊允许您升级!

2. 改进表统计

在基本顺序扫描中,预期行数与实际行数之间存在意外的 10% 不匹配:

对成员 c 进行 Seq 扫描(成本=0.00..1814029.74行=24855474宽度=10)(实际时间=1.132..188654.555行=27307060循环=1)

一点也不引人注目,但仍然不应该出现在这个查询中。表示您可能需要调整您的autovacuum设置 - 对于非常大的桌子来说可能是每张桌子。

问题比较多:

哈希反连接(成本=2246088.17..2966677.08行=1875003宽度=12)(实际时间=209406.338..209406.338rows=0循环=1)

Postgres 期望找到 1875003 行要删除,而实际上找到 0 行。这是出乎意料的。也许大幅提高统计目标members.contact_id and contacts.id可以帮助缩小差距,从而实现更好的查询计划。看:

  • 防止 PostgreSQL 有时选择错误的查询计划

3.避免表和索引膨胀

您的 ~ 25MM 行members占用 23 GB - 每行几乎 1kb,这对于您提供的表定义来说似乎过多(即使您提供的总大小应包括索引):

 4 bytes  item identifier

24        tuple header
 8        null bitmap
36        9x integer
16        2x ts
 1        1x bool
??        1x jsonb

See:

  • 理解 Postgres 行大小

每行 89 个字节 - 或者更少,有一些 NULL 值 - 并且几乎没有任何对齐填充,所以最大 96 字节,加上你的jsonb column.

Either that jsonb列非常大,这让我建议将数据规范化为单独的列或单独的表。考虑:

  • 如何对 JSONB 类型的列执行更新操作

Or你的桌子很臃肿,这可以解决VACUUM FULL ANALYZE或者,当这样做时:

CLUSTER members USING index_members_on_contact_id_and_step_id;
VACUUM members;

但要么在表上获取独占锁,你说你负担不起。pg_repack无需独占锁即可做到。看:

  • VACUUM 将磁盘空间返回给操作系统

即使我们考虑索引大小,您的表似乎太大了:您有 7 个小索引,每个索引每行 36 - 44 字节,没有膨胀,NULL 值更少,因此总共

无论哪种方式,请考虑更具攻击性autovacuum设置为您的餐桌members。有关的:

  • PostgreSQL 上的主动 Autovacuum
  • 缓存表的填充因子是多少?

和/或从一开始就停止让表格膨胀。您是否经常更新行?您经常更新哪个特定专栏?那jsonb专栏也许?您可以将其移动到一个单独的(1:1)表,只是为了停止用死元组使主表膨胀 - 并保持autovacuum从做它的工作。

4. 尝试 BRIN 索引

块范围索引需要 Postgres9.5 或更高版本 and 戏剧性地减小索引大小。我对初稿过于乐观了。 BRIN 指数是perfect对于您的用例,如果您有many行中members对于每个contact.id- 后物理集群至少一次(参见 ③ 的装修CLUSTER命令)。在这种情况下,Postgres 可以快速排除整个数据页。但你的数字表明每个只有大约 8 行contact.id,因此数据页通常会包含多个值,这会消除大部分效果。取决于您的数据分布的实际细节......

另一方面,就目前情况而言,元组大小约为 1 kb,因此每个数据页只有约 8 行(通常为 8kb)。如果这不是主要的膨胀,那么 BRIN 索引毕竟可能会有所帮助。

但您需要先升级您的服务器版本。参见①。

CREATE INDEX members_contact_id_brin_idx ON members USING BRIN (contact_id);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

优化 Postgres 删除孤立记录 的相关文章

随机推荐

  • 有没有办法在 EmberJS 中将数组传递给 currentWhen ?

    我试图使链接在多个路由上保持 活动 状态 例如 users 和 user 有任何想法吗 您可以重新打开 Ember 的 LinkView 并执行类似的操作 允许 currentWhen 包含空格分隔值 Ember LinkView reop
  • 如何使用 jQuery 更改 css 类规则?

    任何人都可以帮助我吗 我的问题有两个部分 我想做的是使用 jQuery 动态更改 css 类规则 classname color red font size 14px 在上面的示例中 我有一个名为 classname现在使用 jQuery
  • 如何在 mongoDB 中对 $lookup 结果应用条件?

    参考我之前的问题 我有一个关于 lookup 的问题 并添加一些条件 您可以从下面的链接描述中获得有关问题的足够参考 Photo id 1 photo name 1 jpg photo description description 1 a
  • 如何借助 Table 组件显示 JRBeanCollectionDataSource 数据?

    我需要在表组件 JasperReports 中显示 JRBeanCollectionDataSource 数据 这是我的模板 ShowPerson jrxml 文件
  • Bootstrap 3.3.5 中无法点击 form-control-feedback 中的链接

    我正在使用链接form control feedback span从 3 1 1 开始执行 javascript 函数 我正在尝试升级到 3 3 5 但是form control feedback改变了 请参阅工作 JsFiddle 3 1
  • 如何让 pandas.read_csv 不执行任何转换?

    例如 tmp test csv 中的值 即01 02 03 旨在代表strings恰好匹配的 d 与整数相反 In 10 print open tmp test csv read A B C 01 02 03 默认情况下 pandas re
  • 有没有办法使文件输入上的本机“浏览”按钮变大跨浏览器?

    如您所知 您可以使用 hacky 方法制作自己的文件输入控件 将文件输入定位在自定义按钮上并使用opacity 0在文件输入本身上 下图显示了预期的设计 其中文件输入绝对位于浏览按钮上方 正如您所看到的 人造浏览按钮比浏览器 Firefox
  • 如何使用 JQuery 格式化货币[重复]

    这个问题在这里已经有答案了 我正在尝试使用下面的代码来格式化货币 currency keyup function e var val this val val val replace 0 9 g if val length gt 2 val
  • 如何在 Firebase 中保存位置信息

    我正在尝试将位置 纬度和经度 保存为 Firebase 中的键 字段之一 在他们的例子中SF车辆 它们确实展示了如何在存储信息后进行查询 但我的问题是如何首先保存 在他们的博客文章中 GeoFire 走向移动 他们展示了数据的样子 但我如何
  • 本地计算机上的MySQL57服务启动然后停止

    当我尝试启动 SQL Server 时 会弹出一条消息 指出它已启动 但随后停止了 这发生在我的服务器重新启动后 有谁知道我该如何解决这个问题 确保数据目录为用户 网络服务 和管理员设置了完全权限 并且如果目录中有空格 则在目录名称周围加上
  • Twitter 关注者数量

    获取纯文本形式的关注者计数的唯一方法是使用 cURL 吗 或者 Twitter API 是否提供任何此类选项 https api twitter com 1 users lookup json screen name tvdw 我的个人资料
  • 无法监听JavaFX中的KeyEvent

    我希望我的 JavaFX 程序能够响应键盘事件 我尝试将侦听器添加到 rootPane 到最上面Pane 但它不响应事件 这是我的代码 AnchorPane root new AnchorPane root setOnKeyPressed
  • C# 6 中(自动)属性初始化语法之间的差异

    C 6 中用于初始化属性的以下表达式有什么区别 1 从构造函数初始化自动属性 public class Context1 public Context1 this Items new List
  • 通过套接字传输文件,最终大小较少字节

    我正在尝试通过 C 中的套接字接收一些文件 但是服务器向我发送了 1000000 字节文件的 64 字节数据包 并且我在目标文件上获得了大约 999902 字节 while n read sd buffer in BUFSIZE BUFSI
  • 如何在非唯一列中按日期对 pandas DataFrame 条目进行分组

    一只熊猫DataFrame包含名为 date 包含非唯一的datetime价值观 我可以使用以下方法对该框架中的线条进行分组 data groupby data date 然而 这将数据分割为datetime价值观 我想按 日期 列中存储的
  • O_DIRECT 的真正含义是什么?

    如果我打开一个文件O DIRECT标志 这是否意味着每当对该文件的写入 阻塞模式 返回时 数据都在磁盘上 这个答案适用于 Linux 其他操作系统可能有不同的警告 语义 我们先从子问题开始 如果我打开带有 O DIRECT 标志的文件 是否
  • python如何解释带有前导零的数字

    我是 python 新手 我正在使用 python 2 7 当我在 python shell 上输入以下内容时 print 01 print 010 print 0100 print 01000 它给出了这个结果 1 8 64 512 我试
  • Windows Azure 中角色的静态 IP 地址?

    有谁知道是否可以在 Windows Azure 上获取 Web 或辅助角色的静态 IP 地址 可能仅在私人测试版中 几年后 Azure 现在允许您为虚拟机和云服务 Web 和辅助角色 保留 IP 地址 然而 目前只能通过 PowerShel
  • 将对象传递给 Switch Case Javascript

    我想要一个 switch case 语句来接受 Javascript 中的对象 该函数看起来像这样 const validate values gt values is an object can be accessed like so v
  • 优化 Postgres 删除孤立记录

    取下面两张表 Table public contacts Column Type Modifiers Storage Stats target Description id integer