将可为空的列添加到表的时间超过10分钟


11

我在表上添加新列时遇到问题。
我尝试运行几次,但是运行了十多分钟后,由于锁定时间,我决定取消查询。

ALTER TABLE mytable ADD mycolumn VARCHAR(50);

有用的信息:

  • PostgreSQL版本:9.1
  • 行数:〜250K
  • 列数:38
  • 可为空的列数:32
  • 约束数量:5(1 PK,3 FK,1 UNIQUE)
  • 索引数:1
  • 操作系统类型:Debian Squeeze 64

我发现了有关PostgreSQL管理可空列的方式的有趣信息(通过HeapTupleHeader)。

我的第一个猜测是,因为此表已经具有8位的32个可空列MAXALIGN,所以HeapTupleHeader的长度为4个字节(未经验证,我不知道该怎么做)。

因此,添加新的可为空的列可能需要在每行上更新HeapTupleHeader以添加新的8位MAXALIGN,这可能会导致性能问题。

因此,我尝试更改可为空的列之一(实际上并不是真正可为空的),以便将可为空的列的数量减少到31,以检查我的猜测是否正确。

ALTER TABLE mytable ALTER myothercolumn SET NOT NULL;

不幸的是,这种更改也需要很长时间,超过5分钟,因此我也中止了它。

您是否知道会导致这种性能损失的原因?


1
好吧,我可以告诉你其中一部分:将列类型更改为与二进制不兼容的另一种类型实际上会创建一个新列,复制数据,并将旧列设置为已删除。但是,SET NOT NULL不更改类型,它只是添加一个约束-但是必须对照表检查该约束,这需要全表扫描。9.4通过使用较弱的锁来改进其中一些情况,但是它仍然是重量级的。
Craig Ringer 2014年

1
在怀疑它运行缓慢之前,您需要确保ALTER TABLE不只是在等待锁。如果已检查,请在问题中提及。
DanielVérité2014年

谢谢克雷格和丹尼尔。当我运行alter命令时,它在pg_stat_activity中显示为等待“ true”,我想这意味着它正在等待锁定!这是检查的好方法吗?顺便说一句,在运行此变更之前,一切正常,但是启动后几秒钟,锁的数量增加了

请尝试访问wiki.postgresql.org/wiki/Lock_dependency_information以获得更好的视图。要么您有遗忘的事务遗忘了提交,要么是由于该表的繁忙活动使它始终处于繁忙状态。
DanielVérité2014年

可能更适合dba.SE。
Erwin Brandstetter

Answers:


8

这里有一些误解:

空位图堆元组报头的一部分。每个文档:

有一个固定大小的标头(在大多数计算机上占23个字节),后跟一个可选的空位图...

您的32个可为空的列是不可疑的,原因有两个:

  • 仅在该行中至少有一个实际值时,才为每行添加空位图。可空列没有直接影响,只有实际值才有影响。如果分配了空位图,则始终会完全分配(全部或全部)。空位图的实际大小是每列1位,四舍五入到下一个字节根据当前的源代码:NULLNULL

    #define BITMAPLEN(NATTS) (((int)(NATTS) + 7) / 8)
  • 空位图在堆元组头之后分配,然后是可选的OID,然后是行数据。OID或行数据的开始t_hoff在标头中由指示。每个注释源代码

    请注意,t_hoff必须是MAXALIGN的倍数。

  • 堆元组标头后有一个空闲字节,占23个字节。因此,行数最多为8列的空位图实际上不会产生任何额外费用。在表的第9列中,t_hoff又增加了一个MAXALIGN(通常为8个)字节,以提供另外64列。因此,下一个边框将是72列。

要显示PostgreSQL数据库集群(包括MAXALIGN)的控制信息,例如在Debian计算机上典型安装Postgres 9.3的示例:

    sudo /usr/lib/postgresql/9.3/bin/pg_controldata /var/lib/postgresql/9.3/main

我更新了您引用的相关答案中的说明

除此之外,即使您的ALTER TABLE语句触发了整个表的重写(它可能会执行此操作,更改了数据类型),但250K的确不是那么多,并且在任何中途的机器上只需几秒钟的时间(除非行异常大) 。10分钟或更长时间表示完全不同的问题。您的陈述很可能正在等待锁定表。

中越来越多的条目pg_stat_activity意味着更多的未完成事务-表示必须等待操作完成的表(最可能)上的并发访问。

黑暗中的几枪

检查可能的表膨胀,尝试轻度VACUUM mytable或更积极一些VACUUM FULL mytable-可能会遇到相同的并发问题,因为此表单还获得了独占锁。您可以尝试pg_repack ...

我将从检查索引,触发器,外键或其他约束(尤其是涉及该列的约束)的可能问题开始。特别是可能涉及到损坏的索引?尝试REINDEX TABLE mytable;DROP全部尝试并ALTER TABLE 在同一事务中重新添加它们。

尝试在夜间或没有太大负载的情况下运行命令。

暴力破解方法是停止访问服务器,然后重试:

如果无法确定,升级到当前版本或即将推出的9.4 可能会有所帮助。大表和锁定详细信息已有一些改进。但是,如果您的数据库中有损坏的东西,您可能应该首先弄清楚。


2
几乎可以肯定是锁。但是,作为测试,您始终可以创建表的副本并尝试对其进行更改。如果那不会花费很长时间,那么您就会知道问题出在实际修改上。

感谢您的解释欧文。我认为您是对的,这似乎是一个锁定问题。当我检查pg_stat_activity时,我可以看到我的ALTER具有“ waiting”(正在等待)的true。我不明白的是为什么ALTER不能在表上获得锁,原因是即使我找不到正在运行的任何查询,它也似乎无法获得它。但是,一旦我的ALTER开始运行,所有其他查询都在等待它完成。因此,该活动似乎表明ALTER锁定了所有其他查询,但也表明ALTER没有获得该锁定。我认为有些事情我不太了解!

@MatthieuVerrecchia:您是否尝试过Richard建议的测试?
Erwin Brandstetter'9

1
我只是将表克隆到了一个新表(使用pg_dump-> pg_sql)。在50毫秒内正确添加了新列,这确认了锁定问题。顺便说一句,仍然不明白为什么ALTER不能通过真正的标准db活动获得锁定。

1
@ErwinBrandstetter我按照您的建议尝试了VACUUM,然后尝试了REINDEX。REINDEX也在阻塞,原因是它也无法获取锁。.经过一些调查,问题比我们认为的要简单..剩余一个星期的<IDLE>与打开的事务已解决问题,谢谢对于所有内容,信息都非常有用。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.