ALTER COLUMN TYPE varchar(N) 是否重写 Postgres 9.6 中的 table?

Does ALTER COLUMN TYPE varchar(N) rewrite the table in Postgres 9.6?

过去

我们用 Postgres 8.4 处理这个问题的方法是手动更新 pg_attribute table:

LOCK TABLE pg_attribute IN EXCLUSIVE MODE;
UPDATE pg_attribute SET atttypmod = 104
    WHERE attrelid = 'table_name'::regclass AND
          attname = 'column_name';

column_name 是一个 varchar(50) 而我们想要一个 varchar(100),但是 table 太大了(数千万行)并且被过度使用以重写.

如今

对于这样一个(至少是传闻中的)常见问题,围绕该主题的内容和答案很少而且已经过时。

但是,在看到至少 3 次讨论可能是这种情况的提示后,我开始认为使用较新版本的 Postgres(我们使用的是 9.6),您现在可以 运行 以下:

ALTER TABLE 'table_name' ALTER COLUMN 'column_name' TYPE varchar(100);

...无需重写 table.

这是正确的吗?

如果是这样,您知道 Postgres 文档中关于该主题的一些权威信息吗?

ALTER TABLE 不需要重写。

The documentation 说:

Adding a column with a DEFAULT clause or changing the type of an existing column will require the entire table and its indexes to be rewritten.

测试起来很简单:
尝试使用空 table 并查看 pg_class 行中 table 的 relfilenode 列是否更改:

SELECT relfilenode FROM pg_class
    WHERE relname = 'table_name';

继续阅读文档,您会看到:

As an exception, when changing the type of an existing column, if the USING clause does not change the column contents and the old type is either binary coercible to the new type or an unconstrained domain over the new type, a table rewrite is not needed; but any indexes on the affected columns must still be rebuilt.

由于 varchar(50) 显然可以二进制强制转换为 varchar(100),您的案例不需要 table 重写,正如上面的测试所证实的那样。

根据 What's new in PostgreSQL 9.2 以上答案至少对我来说很奇怪 已接受的答案经过编辑以与以下内容保持一致:

Reduce ALTER TABLE rewrites

A table won't get rewritten anymore during an ALTER TABLE when changing the type of a column in the following cases:

varchar(x) to varchar(y) when y>=x. It works too if going from varchar(x) to varchar or text (no size limitation)

我用 postgres 10.4 测试过 relfilenode 在 运行 alter table ... alter column ... type varchar(50)

之后保持不变
create table aaa (field varchar(10));
insert into aaa select f from generate_series(1,1e6) f;
commit;
SELECT oid, relfilenode FROM pg_class WHERE relname = 'aaa';
alter table aaa alter column field type varchar(50);
commit;
SELECT oid, relfilenode FROM pg_class WHERE relname = 'aaa';

我不确定为什么你在 9.6 中有不同的 relfilenode(或者我遗漏了一些东西...)。