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

229

我有一个关于对一张非常大的表(近3000万行)使用ALTER TABLE命令的问题。 其中一个列是varchar(255)类型,我想将其调整为varchar(40)类型。 基本上,我想通过运行以下命令来更改我的列:

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

我的问题不在于进程时间很长,而是在执行ALTER TABLE命令期间,我的表似乎不再可读。 有没有更聪明的方法?也许可以添加一个新列,将旧列中的值复制到新列,删除旧列,最后重命名新列?

注意:我使用的是PostgreSQL 9.0。


17
明确一下:你知道,resizing 不会使表格占用更少的空间吗? - A.H.
1
即使在我的情况下呢?我的意思是,该列的最大大小将为40个字符(因此是八位字节),而不是255个字符? - Labynocle
26
如果你在PostgreSQL中使用varchar(255),它并不会为一个实际长度为40字节的值分配255个字节。它会分配40个字节(加上一些内部开销)。ALTER TABLE唯一会改变的是该列可以存储的最大字节数,超过此数将出现PG错误。请注意不要改变原文意思,并使翻译通俗易懂。 - A.H.
1
关于A.H.提到的开销:varchar(n)的开销是多少? - Erwin Brandstetter
2
请查看此处的答案以获取更新 https://dba.stackexchange.com/questions/189890/does-changing-the-length-limit-type-modifier-of-varchar-result-in-a-table-or - Evan Carroll
9个回答

178

9
请注意,这仅适用于您指定了一个更大的尺寸(30 > 10)。如果尺寸更小,您将��得到与我的错误相同的错误 - Matthieu
6
除非有一个或多个行的值超过了新大小,否则Postgres不应该在通过ALTER TABLE查询降低varchar大小时抛出错误。 - Tell
1
@Tell,很有趣。这是否意味着Postgres会对表进行全面扫描,或者以某种方式在其统计信息中保留最大大小? - Matthieu

92

这里有一个如何在不改变数据的情况下调整PostgreSQL表中列大小的描述。你需要修改数据库目录数据。官方唯一的方法是使用ALTER TABLE,但正如你所注意到的,该更改将在运行时锁定并重写整个表。

在更改之前,请确保阅读文档中的字符类型部分。这里有各种奇怪的情况需要注意。长度检查是在将值存储到行中时进行的。如果您在其中进行了较低限制的修改,则不会减少现有值的大小。在进行更改后,您应该对整个表进行扫描,查找字段长度大于40个字符的行。您需要手动截断这些行,因此您又回到了一些锁定问题,只是针对超大的行。因为如果有人尝试更新该行上的任何内容,它将在存储新版本的行时拒绝它,因为它现在太大了。这会给用户带来麻烦。

VARCHAR是PostgreSQL中存在的可怕类型,仅为了遵守其关联的SQL标准中的可怕部分。如果您不关心多数据库兼容性,请考虑将数据存储为TEXT并添加限制其长度的约束。您可以在不涉及表锁定/重写问题的情况下更改约束,并且它们可以进行比弱长度检查更多的完整性检查。


1
谢谢您的回答。我会查看您提供的链接。我不担心手动大小检查,因为我的所有内容都有一个最大长度为40个字符。我需要更多地了解关于TEXT约束的信息,因为我认为VARCHAR更适合检查长度 :) - Labynocle
9
修改 varchar 长度不会重写表格,它只是像检查约束一样检查整个表格的长度限制。如果你增加了长度,那就没有什么要做的,接下来的插入或更新操作将允许更长的长度。如果你减少了长度,并且所有行都通过了新的更小限制,PostgreSQL 不会采取任何进一步的措施,只会允许下一个插入或更新操作写入新的长度。 - Maniero
3
@bigown,只是为了澄清,你的陈述仅适用于PostgreSQL 9.2+,而不是旧版本。 - MatheusOl
需要注意的是,此命令所需的权限比“ALTER TABLE”更高,因此根据最小特权原则,这不是最佳解决方案。 - anishtain4

63

好的,我可能来晚了,但是...

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

与其他一些数据库不同,Postgres聪明地只使用足以适应字符串的空间(甚至对于较长的字符串使用压缩),因此即使您的列声明为VARCHAR(255)-如果您将40个字符的字符串存储在列中,空间使用量将是40个字节+ 1个字节的开销。

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

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

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


10
补充“为什么”的信息永远不会太晚!感谢提供所有这些信息。 - Labynocle
有时候,您需要在数据库结构上保持一致性。即使两个列之间没有关系,在概念上也可以存在关联,例如查看EAV模型。 - Alexandre
是的,但你正在过度计算事务。想一想,你声明了一个尺寸为1024的列,并且你存储了10K行具有可变数量的尺寸。你让RDBMS(在这种情况下是Postgres)计算你的列的“最佳”大小。 - Franco Gil
@FrancoGil:首先,我无法想象“计算最佳大小”的开销除了绝对微不足道之外还有什么。其次,Postgres会在您定义列的方式无论如何时都执行此操作,如果存储相同的数据,则VARCHAR(40)和VARCHAR(1024)的磁盘格式将相同。 - Sergey
你刚刚帮我赢得了宝贵的公关时间!谢谢你的回答。 - undefined

59

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

最终我使用了ALTER TABLE中的USING语句:

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

正如@raylu所指出的那样,ALTER会对表进行独占锁定,因此在完成之前,所有其他操作都将被延迟。


3
ALTER 语句会对表进行独占锁定,阻止其他所有操作。 - raylu

12

如果您将 ALTER 操作放入事务中,则不应锁定表:

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

这个方法对我来说非常快,处理超过 400k 行的表只需要几秒钟。


5
你为什么期望显式事务包装器会改变ALTER语句的锁定行为呢?它并不会。 - Erwin Brandstetter
尝试使用事务包装器和不使用事务包装器,你会注意到巨大的区别。 - jacktrade
3
你的答案在原则上是不正确的。任何没有显式事务包装器的DDL语句都会隐式地在事务内运行。显式事务的唯一可能影响是锁定时间更长,直到显式提交。包装器只有在您想将更多命令放入同一事务中时才有意义。 - Erwin Brandstetter
你完全是正确的,但我坚持:自己尝试一下,然后问为什么不能以同样的方式运行。 - jacktrade
在Postgres 9.3上没有帮助。 - Noumenon

10

对于我来说,在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所描述的页面的缓存。如果那个也失效了,修改语句如下:

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

如果您的表格是TABLE1,列是COL1,并且您想将其设置为35个字符(根据链接所述,可能需要+4用于遗留目的,也许是评论中A.H所提到的开销)。


5
请尝试运行以下alter table:
ALTER TABLE public.users 
ALTER COLUMN "password" TYPE varchar(300) 
USING "password"::varchar;

2
我找到了一种非常简单的方法来更改大小,即更改 "import javax.validation.constraints.Size;" 的注释"@Size(min = 1,max = 50)"。
@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

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