|
| 1 | +# How to drop a column |
| 2 | + |
| 3 | +>我每天都会发布一篇新的 PostgreSQL "howto" 文章。加入我的旅程吧 — 订阅、提供反馈、分享! |
| 4 | +
|
| 5 | +删除一列很简单: |
| 6 | + |
| 7 | +```sql |
| 8 | +alter table t1 drop column c1; |
| 9 | +``` |
| 10 | + |
| 11 | +然而,需要注意在某些情况下可能出现的一些复杂情况。 |
| 12 | + |
| 13 | +## 风险1:应用代码未准备好 |
| 14 | + |
| 15 | +应用代码需要停止使用该列。这意味着代码需要先部署。 |
| 16 | + |
| 17 | +## 风险2:部分停机 |
| 18 | + |
| 19 | +在高负载下,如果没有设置较低的 `lock_timeout` 和重试机制,执行此类 `ALTER` 操作是一个糟糕的主意,因为该语句需要获取表的 `AccessExclusiveLock` 。如果尝试获取锁的时间较长 (例如,由于现有事务持有此表上的任何一个锁 — 可能是读取表中一行数据的事务,或者是为了防止事务 ID 回卷而处理该表的`autovacuum`),这将对当前所有查询造成阻塞,导致项目在高负载下出现部分停机。 |
| 20 | + |
| 21 | +解决方案:使用较低的 `lock_timeout` 和重试机制。以下是一个示例 (有关此问题的更多详细信息和更进阶的示例,请参照 [zero-downtime Postgres schema migrations need this: lock_timeout and retries](https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries)): |
| 22 | + |
| 23 | +```sql |
| 24 | +do $do$ |
| 25 | +declare |
| 26 | + lock_timeout constant text := '50ms'; |
| 27 | + max_attempts constant int := 1000; |
| 28 | + ddl_completed boolean := false; |
| 29 | +begin |
| 30 | + |
| 31 | + perform set_config('lock_timeout', lock_timeout, false); |
| 32 | + |
| 33 | + for i in 1..max_attempts loop |
| 34 | + begin |
| 35 | + execute 'alter table t1 drop column c1'; |
| 36 | + ddl_completed := true; |
| 37 | + exit; |
| 38 | + exception |
| 39 | + when lock_not_available then |
| 40 | + null; |
| 41 | + end; |
| 42 | + end loop; |
| 43 | + |
| 44 | + if ddl_completed then |
| 45 | + raise info 'DDL successfully executed'; |
| 46 | + else |
| 47 | + raise exception 'DDL execution failed'; |
| 48 | + end if; |
| 49 | +end $do$; |
| 50 | +``` |
| 51 | + |
| 52 | +请注意,在此示例中,子事务是隐式使用的 (`BEGIN/EXCEPTION WHEN/END`块)。在高 `XID` 增长率的情况下 (例如,有大量写入事务) 和长时间运行的事务中,这可能成为一个问题 — 可能会在从库上触发`SubtransSLRU` 的争用 (参照:[PostgreSQL Subtransactions Considered Harmful](https://postgres.ai/blog/20210831-postgresql-subtransactions-considered-harmful))。在这种情况下,应在事务级别实现重试逻辑。 |
| 53 | + |
| 54 | +## 风险3:错误地预期数据会被删除 |
| 55 | + |
| 56 | +最后,在不同环境之间复制数据并删除敏感数据时,请记住 `ALTER TABLE ... DROP COLUMN ...` 并不安全,它不会真正删除数据。删除列 `c1` 后,元数据中仍然存在相关信息: |
| 57 | + |
| 58 | +```sql |
| 59 | +nik=# select attname from pg_attribute where attrelid = 't1'::regclass::oid order by attnum; |
| 60 | + attname |
| 61 | +------------------------------ |
| 62 | + tableoid |
| 63 | + cmax |
| 64 | + xmax |
| 65 | + cmin |
| 66 | + xmin |
| 67 | + ctid |
| 68 | + id |
| 69 | + ........pg.dropped.2........ |
| 70 | +(8 rows) |
| 71 | +``` |
| 72 | + |
| 73 | +超级用户可以轻松恢复该列: |
| 74 | + |
| 75 | +```sql |
| 76 | +nik=# update pg_attribute |
| 77 | + set attname = 'c1', atttypid = 20, attisdropped = false |
| 78 | + where attname = '........pg.dropped.2........'; |
| 79 | +UPDATE 1 |
| 80 | +nik=# \d t1 |
| 81 | + Table "public.t1" |
| 82 | + Column | Type | Collation | Nullable | Default |
| 83 | +--------+---------+-----------+----------+--------- |
| 84 | + id | bigint | | | |
| 85 | + c1 | bigint | | | |
| 86 | +``` |
| 87 | + |
| 88 | +一些解决方法: |
| 89 | + |
| 90 | +- 在删除列后使用 `VACUUM FULL` 重建表。在这种情况下,虽然尝试恢复会成功,但数据将不会存在。 |
| 91 | +- 考虑使用受限用户和列级别的权限,而不是删除列。列和数据仍然存在,但用户无法读取。当然,如果严格要求删除数据,此方法不适用。 |
| 92 | +- 在删除列之后转储/恢复。 |
0 commit comments