Skip to content

Commit 0ac5d1c

Browse files
author
xiongcc
committed
添加第16章:How to get into trouble using some Postgres features
1 parent 6c14eaa commit 0ac5d1c

File tree

1 file changed

+86
-0
lines changed

1 file changed

+86
-0
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# How to get into trouble using some Postgres features
2+
3+
>我每天都会发布一篇新的 PostgreSQL "howto" 文章。加入我的旅程吧 — 订阅、提供反馈、分享!
4+
5+
今天我们有一些相当有趣的素材,但了解 (并避免) 这些问题可以节省您的时间和精力。
6+
7+
## NULLs
8+
9+
`NULL` 值虽然很常见,但在 SQL 中是导致麻烦的主要原因,Postgres 也不例外。
10+
11+
例如,有人可能会忘记连接操作符 (||)、算术运算符 (*、/、+、-)、传统比较运算符 (=、<、>、<=、>=、<>) 都不是 NULL-safe 的操作,结果可能会丢失。
12+
13+
尤其是当你建立一家初创公司并且一些重要的业务逻辑依赖于它时,查询不能正确处理 NULL,导致用户群或金钱或时间 (或所有) 的损失:
14+
15+
```sql
16+
nik=# \pset null ∅
17+
Null display is "".
18+
19+
nik=# select null + 1;
20+
?column?
21+
----------
22+
23+
24+
25+
(1 row)
26+
```
27+
28+
`NULL` 值真的很危险,即使是经验丰富的工程师在处理它们时也常常会遇到问题。以下是一些可以帮助你的素材:
29+
30+
- [NULLs: the good, the bad, the ugly, and the unknown](https://postgres.fm/episodes/nulls-the-good-the-bad-the-ugly-and-the-unknown) (podcast)
31+
- [What is the deal with NULLs?](http://thoughts.davisjeff.com/2009/08/02/what-is-the-deal-with-nulls/)
32+
33+
一些技巧 — 如何使代码成为 NULL-safe 的:
34+
35+
- 考虑使用表达式如 `COALESCE(val, 0)``NULL` 值替换为某个值 (通常是 `0``''`)。
36+
- 在比较时,使用 `IS [NOT] DISTINCT FROM` 代替 `=``<>` (不过请查看 `EXPLAIN` 执行计划)。
37+
- 进行连接时使用 `format('%s %s', var1, var2)`
38+
- 不要使用 `WHERE NOT IN (SELECT ...) `— 使用 `NOT EXISTS `代替 (参照 [JOOQ blog post](https://jooq.org/doc/latest/manual/reference/dont-do-this/dont-do-this-sql-not-in/))。
39+
- 要小心,`NULL` 值是狡猾的。
40+
41+
## 在高负载下使用子事务
42+
43+
如果你希望达到数十万或数百万 TPS 并遇到各种问题,请使用子事务。你可能会隐式使用到它们 — 例如,如果使用了 Django、Rails 或 PL/pgSQL 中的`BEGIN/EXCEPTION`块。
44+
45+
为什么要完全摆脱子事务的原因:[PostgreSQL Subtransactions Considered Harmful](https://postgres.ai/blog/20210831-postgresql-subtransactions-considered-harmful)
46+
47+
## int4 主键
48+
49+
如果表有 10 亿行,要想将 `int4` 主键 (也称为 int、integer) 零停机时间转换为 `int8` 主键时,所需的工作量极其之大。由于对齐填充,表 (`id int4, created_at timestamptz`) 将占用与 (`id int8, created_at timestamptz`) 相同的磁盘空间。
50+
51+
## (特殊的) SELECT INTO并不像你想的那样
52+
53+
有一天我在调试 PL/pgSQL 函数时,复制粘贴了一条查询,像这样,运行在 psql 中:
54+
55+
```sql
56+
nik=# select * into var from t1 limit 1;
57+
SELECT 1
58+
```
59+
60+
它工作了!这真是一个巨大的惊喜 — 在 SQL 上下文中,[SELECT INTO](https://postgresql.org/docs/current/sql-selectinto.html) 是一个 DDL 命令,它会创建一个表并将数据插入其中 (难道这不应该被弃用吗?)。
61+
62+
## 认为"事务性DDL"很简单
63+
64+
是的,Postgres 具有"事务性 DDL",你可以从中获益良多 — 直到您无法这样做。在高负载下,你无法依赖它 — 而是需要开始使用零停机时间的方法,避免错误 (阅读:[常见的数据库模式更改错误](https://postgres.ai/blog/20220525-common-db-schema-change-mistakes),并依赖于"非事务性" DDL,比如 `CREATE INDEX CONCURRENTLY`,某些操作可能会失败,之后需要清理再重试)。
65+
66+
在高负载下进行 DDL 部署的大问题是,默认情况下,你可能会在尝试部署一个非常轻的模式更改时遇到停机 — 除非实现了带有低 `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))。
67+
68+
## 一次删除大量行
69+
70+
这是一个陷入麻烦的好方法:发出 `DELETE` 数百万行的命令并等待。如果检查点没有调优 (`max_wal_size = 1GB`),如果元组通过 IndexScan 被删除 (意味着使页面变脏的过程相当"随机"),并且磁盘 IO 受到限制,这可能会让你的系统崩溃。即使它幸存下来,你也会碰到:
71+
72+
- 锁定问题的风险 (`DELETE` 阻止其他用户发出的写入)
73+
- 生成了大量死元组,这些死元组稍后会被 `autovacuum` 转换为膨胀。
74+
75+
解决方法:
76+
77+
- 拆分为批次,
78+
- 如果大量写入不可避免,请考虑临时提高 `max_wal_size`,这不需要重启 (不过:如果服务器在此过程中崩溃,可能会增加恢复时间)。
79+
80+
阅读 [common db schema change mistakes](https://postgres.ai/blog/20220525-common-db-schema-change-mistakes#case-4-unlimited-massive-change).
81+
82+
## 其他"不要做"文章
83+
84+
- [Depesz: Don’t do these things in PostgreSQL](https://depesz.com/2020/01/28/dont-do-these-things-in-postgresql/)
85+
- [PostgreSQL Wiki: Don't Do This](https://wiki.postgresql.org/wiki/Don't_Do_This)
86+
- [JOOQ: Don't do this](https://jooq.org/doc/latest/manual/reference/dont-do-this/)

0 commit comments

Comments
 (0)