PostgreSQL-将varchar列的大小更改为较短的长度


153

ALTER TABLE对一个非常大的表(近3000万行)上的命令有疑问。它的一列是a varchar(255),我想将其调整为a varchar(40)。基本上,我想通过运行以下命令来更改列:

ALTER TABLE mytable ALTER COLUMN mycolumn TYPE varchar(40);

如果过程很长,我没有问题,但是在ALTER TABLE命令期间似乎我的表不再可读。有没有更聪明的方法?也许添加一个新列,从旧列中复制值,删除旧列,最后重命名新列?

任何线索将不胜感激!提前致谢,

注意:我使用PostgreSQL 9.0。


11
只是要清楚一点:您知道resizing吗,这不会使表占用更少的空间吗?
AH

我的意思是该列的最大大小为40个字符(所以八位字节),而不是255个?
Labynocle 2011年

16
如果您varchar(255)对PostgreSQL 说的话,它将不会为实际长度为40个字节的值分配255个字节。它将分配40个字节(加上一些内部开销)。be changed by the ALTER TABLE` 唯一会改变的是您可以在该列中存储的最大字节数,而不会导致PG出错。
AH


点击这里,查看答案的更新dba.stackexchange.com/questions/189890/...
埃文·卡罗尔

Answers:


73

不更改data的情况下,在PostgreSQL表中的列调整大小时,有关于如何执行此操作的描述。您必须修改数据库目录数据。正式执行此操作的唯一方法是使用ALTER TABLE,并且您已经注意到,更改将在运行时锁定并重写整个表。

更改此内容之前,请确保您已阅读文档的“ 字符类型”部分。这里要注意各种奇怪的情况。将值存储到行中时,将进行长度检查。如果您在其中限制了一个较低的限制,那根本不会减少现有值的大小。进行更改后,最好对整个表进行扫描以查找字段长度大于40个字符的行。您将需要弄清楚如何手动截断这些锁-这样您就只能锁住一些过大的锁-因为如果有人试图更新该行上的任何内容,那么此时它会拒绝太大的锁它会存储该行的新版本。随之而来的是用户热闹。

VARCHAR是一种可怕的类型,仅在PostgreSQL中存在以遵守其与SQL标准相关的可怕部分。如果您不关心多数据库兼容性,请考虑将数据存储为TEXT并添加约束以限制其长度。您可以更改约束而不会出现此表锁定/重写问题,并且约束可以执行更多的完整性检查,而不仅仅是弱长度检查。


谢谢你的回答。我会检查您的链接。我不担心手动大小检查,因为我所有内容的最大大小为40个字符。我需要阅读更多有关TEXT约束的信息,因为我认为VARCHAR更好地检查lentgh :)
Labynocle

6
更改varchar长度不会重写该表。它只是严格按照CHECK CONSTRAINT检查整个表的约束长度。如果增加了长度,则无需执行任何操作,仅下一次插入或更新将接受更大的长度。如果减小长度,并且所有行都通过新的较小约束,则Pg除了允许下一次插入或更新仅写入新长度之外,不会采取任何其他措施。
Maniero

3
@bigown,请澄清一下,您的声明仅适用于PostgreSQL 9.2+,而不适用于旧版本。
MatheusOl

12
现在链接已死。
raart's

有关如何更多信息,这个作品退房dba.stackexchange.com/questions/189890/...
埃文·卡罗尔

100

在PostgreSQL 9.1中,有一种更简单的方法

http://www.postgresql.org/message-id/162867790801110710g3c686010qcdd852e721e7a559@mail.gmail.com

CREATE TABLE foog(a varchar(10));

ALTER TABLE foog ALTER COLUMN a TYPE varchar(30);

postgres=# \d foog

 Table "public.foog"
 Column |         Type          | Modifiers
--------+-----------------------+-----------
 a      | character varying(30) |

6
请注意,它仅在您指定更大的尺寸(30> 10)时才起作用。如果尺寸较小,则会收到与我相同的错误
Matthieu

2
如果通过ALTER TABLE查询降低varchar大小,则Postgres 不会引发错误,除非更多行中的一个包含的值超过新大小。
告诉

@告诉,有趣。这是否意味着Postgres会对表进行完整扫描,或者以某种方式将最大大小保留在其统计信息中?
Matthieu

47

好吧,我可能晚会晚了,但是...

在您的情况下,无需调整列大小!

与其他一些数据库不同,Postgres非常聪明,仅使用足够的空间来容纳字符串(甚至对更长的字符串使用压缩),因此即使将列声明为VARCHAR(255)-如果您在其中存储40个字符的字符串列,空间使用量将为40字节+ 1字节的开销。

短字符串(最多126个字节)的存储要求是1个字节加上实际的字符串,其中包括在字符情况下的空格填充。较长的字符串的开销为4个字节,而不是1个字节。较长的字符串由系统自动压缩,因此对磁盘的物理需求可能会更少。非常长的值也存储在后台表中,这样它们就不会干扰对较短列值的快速访问。

http://www.postgresql.org/docs/9.0/interactive/datatype-character.html

VARCHAR中的大小规范仅用于检查所插入值的大小,而不会影响磁盘布局。实际上,VARCHAR和TEXT字段以相同的方式存储在Postgres中


8
添加有关“为什么”的更多信息永远不会太晚!感谢您提供所有这些信息
Labynocle

有时您需要数据库结构保持一致。即使2列没有关系,从概念的角度来看它们也可以有关系,例如检出模型EAV。
2014年

36

我在尝试将VARCHAR从32截断为8并得到时遇到了同样的问题ERROR: value too long for type character varying(8)。我想尽可能地接近SQL,因为我使用的是自制的类似JPA的结构,我们可能必须根据客户的选择切换到不同的DBMS(PostgreSQL是默认的)。因此,我不想使用更改系统表的技巧。

我结束了USING在中使用语句ALTER TABLE

ALTER TABLE "MY_TABLE" ALTER COLUMN "MyColumn" TYPE varchar(8)
USING substr("MyColumn", 1, 8)

正如@raylu所指出的,ALTER在表上获取一个排他锁,因此所有其他操作将延迟到完成为止。


2
ALTER获取在桌子上的排他锁,并防止所有其他操作
raylu 18/09/21

8

在redshift postgresql上,添加新列并用旧列替换对我有用,有关更多详细信息,请参考此链接https://gist.github.com/mmasashi/7107430

BEGIN;
LOCK users;
ALTER TABLE users ADD COLUMN name_new varchar(512) DEFAULT NULL;
UPDATE users SET name_new = name;
ALTER TABLE users DROP name;
ALTER TABLE users RENAME name_new TO name;
END;

7

这是Greg Smith描述的页面缓存。如果也死了,则alter语句如下所示:

UPDATE pg_attribute SET atttypmod = 35+4
WHERE attrelid = 'TABLE1'::regclass
AND attname = 'COL1';

在表为TABLE1的情况下,列为COL1,并且您希望将其设置为35个字符(根据链接的遗留目的,需要+4,可能是AH在注释中提及的开销)。


7

如果将更改放入事务中,则不应锁定表:

BEGIN;
  ALTER TABLE "public"."mytable" ALTER COLUMN "mycolumn" TYPE varchar(40);
COMMIT;

这对我来说非常有效,在具有40万行的表格上快速浏览数秒。


5
您为什么期望显式事务包装器更改ALTER语句的锁定行为?没有。
欧文·布兰德斯特

试试看,无论是否有事务包装器,您都会发现巨大的不同。
jacktrade

2
您的回答不正确。任何没有显式事务包装器的DDL语句都隐式地在事务内部运行。显式事务的唯一可能效果是将锁保持更长的时间 -直到显式COMMIT。只有在要将更多命令放入同一事务中时,包装程序才有意义。
Erwin Brandstetter

您是完全正确的,但我坚持:尝试一下,继续。然后问为什么不能以相同的方式工作。
jacktrade

对Postgres 9.3没有帮助。
Noumenon

1

我发现一种非常简单的更改大小的方法,即注释@Size(min = 1,max = 50),它是“ import javax.validation.constraints”(即“ import javax.validation.constraints.Size;”)的一部分。

@Size(min = 1, max = 50)
private String country;


when executing  this is hibernate you get in pgAdmin III 


CREATE TABLE address
(
.....
  country character varying(50),

.....

)

谢谢你的帖子!请不要在帖子中使用签名/标语。您的用户名将被视为您的签名,并且您可以使用个人资料发布自己喜欢的任何信息。签名/标语常见问题解答
Andrew Barber

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.