将时间戳列更改为时间戳而不“转换”数据?

Pad*_*han 5 postgresql alter-table timezone

似乎将列从 更改为timestamp without time zonetimestamp with time zone根据语句时的当前会话时区转换现有数据alter

\n

请参阅此示例,每个语句后显示输出

\n
create table tztest (col1 timestamp without time zone);\n\nset timezone = 'UTC';\ninsert into tztest (col1) values ('2023-02-01 10:10:10');\nselect col1 as t1, extract(epoch FROM col1) from tztest;\n-- \xe2\x86\x92 2023-02-01 10:10:10    1675246210\n\nset timezone = 'America/New_York';\nalter table tztest alter column col1 type timestamp with time zone;\nselect col1 as t2, extract(epoch FROM col1) from tztest;\n-- \xe2\x86\x92 2023-02-01 10:10:10-05 1675264210\n\nset timezone = 'UTC';\nselect col1 as t3, extract(epoch FROM col1) from tztest;\n-- \xe2\x86\x92 2023-02-01 15:10:10+00 1675264210\n
Run Code Online (Sandbox Code Playgroud)\n

命令后纪元值发生变化alter
\n我期望 PG 会假设该timestamp值是 UTC,并且在将类型更改为timestamp with time zone(给我t2of05:10:10t3of 10:10:10)时不调整它。

\n

然而,PG 似乎假设 时的会话时区alter,并将其转换为 UTC。

\n

我的理解是否正确,这是预期的行为吗?

\n

在一个大表上,更改将更新每一行,这是我们关心并且当然想理解的事情。

\n

Erw*_*ter 3

是的,你的理解是正确的。如果您没有明确设置,请勿依赖后备(当前会话的时区设置)。
如果您的时间戳应被解释为 UTC 时间戳,请明确说明。最好timezone针对这种特殊情况设置会话并使用ALTER TABLE简单的SET DATA TYPE. 只有这种情况才可以走捷径,无需重写表(见下文):

SET timezone = 'UTC';
ALTER TABLE tztest ALTER COLUMN col1 TYPE timestamptz;
Run Code Online (Sandbox Code Playgroud)

您还可以AT TIME ZONEUSING条款中申请SET DATA TYPE

ALTER TABLE tztest ALTER COLUMN col1 TYPE timestamptz
USING col1 AT TIME ZONE 'UTC';
Run Code Online (Sandbox Code Playgroud)

无论哪种方式,纪元值都保持不变,因为 Postgrestimestamptz在内部存储为 UTC 时间戳。Postgres 必须重建任何涉及的索引col1

小提琴

看:

表重写?

在一个大表上,更改将更新每一行......

除了指出的异常之外,ALTER TABLE重写整个表和所有索引(在持续时间内采取独占锁!)。将类型更改为二进制强制类型(如text--> varchar)可以避免这种情况。手册:

作为例外,当更改现有列的类型时,如果子句USING不更改列内容,并且旧类型可以二进制强制转换为新类型,或者是新类型上的不受约束的域,则不需要表重写。

对于表达式 with ,Postgres 没有意识到这一点AT TIME ZONE但是SET DATA TYPE,就像 Jeff 评论的那样,将会话时区设置为 UTC 的简单方法就符合条件。Postgres 12的发行说明:

当会话时区为 UTC 时,允许ALTER TABLE ... SET DATA TYPE在 之间进行更改timestamp并 避免表重写 (Noah Misch)timestamptz

在 UTC 时区,这两种数据类型是二进制兼容的。

旧版本没有那么智能。比较:

小提琴-- Postgres 11
小提琴-- Postgres 12

另请注意,重写不是UPDATE,它会产生死元组并使表膨胀,但不会以其较弱的锁干扰并发读取。这会在原始状态下完全重写整个表,但在持续时间内采用排他锁。

有关的:

  • 但请注意,在会话时区设置为 UTC 时执行 ALTER TABLE 不会导致表重写(它只是执行二进制强制操作),而您的公式会导致表重写。但如果该列已建立索引,那么该索引无论如何都需要重建。 (3认同)