解锁七大技巧,避免你的 Postgres 挂掉

数据库 PostgreSQL
如果这些事务同时运行,它们很可能会互相卡住,永远无法完成。Postgres 会在一秒钟左右后识别出这种情况,并取消其中一个事务,让另一个事务完成。出现这种情况时,你应该检查一下自己的应用程序,看看能否让事务始终按照相同的顺序进行。

1.永远不要添加带默认值的列

从 PostgreSQL 11 开始,添加带默认值的列不再重写表。本博文中的其他提示仍然有效!

PostgreSQL 的黄金法则是:在生产中向表添加列时,千万不要指定默认值。

添加列会对表加上锁,从而阻塞读写。如果添加的列有默认值,PostgreSQL 会重写整个表,为每一行填写默认值,这对大型表来说可能需要数小时。在此期间,所有查询都会阻塞,数据库将不可用。

不要这样做:

-- 阻塞读写一直到完全重写(以小时计)
ALTER TABLE items ADD COLUMN last_update timestamptz DEFAULT now();

改用这个方法:

-- 阻塞查询,更新,插入,删除直到 catalog 被更新 (毫秒计)
ALTER TABLE items ADD COLUMN last_update timestamptz;
-- 查询,插入可以执行,一些更新和删除在重写表时,会被阻塞
UPDATE items SET last_update = now();

或者更好的办法是,通过小批量更新来避免长时间阻塞更新和删除,例如:

do {
  numRowsUpdated = executeUpdate(
    "UPDATE items SET last_update = ? " +
    "WHERE ctid IN (SELECT ctid FROM items WHERE last_update IS NULL LIMIT 5000)",
    now);
} while (numRowsUpdate > 0);

这样,您就可以在尽量不影响用户的情况下添加和填充新列。

译者按,在 Bytebase 中对应的是这条 SQL 审核规则:

图片图片

2.当心锁队列,使用 lock timeouts (锁超时)

在 PostgreSQL 中,每个锁都有一个队列。如果事务 B 试图获取一个已经被事务 A 持有的有冲突的锁,那么事务 B 将会在锁队列中等待。现在有趣的是:如果另一个事务 C 加入,它不仅需要检查与事务 A 的冲突,还需要检查与事务 B 以及锁队列中其他所有事务的冲突。

这意味着即使你的 DDL 命令可以非常快速地运行,它也可能在队列中等待很长时间,因为需要等待其他查询完成,并且在它之后启动的查询将会被它阻塞。

如果表上可能长时间运行 SELECT 查询,就不要这样做:

ALTER TABLE items ADD COLUMN last_update timestamptz;

而应该这样做:

SET lock_timeout TO '2s'
ALTER TABLE items ADD COLUMN last_update timestamptz;

通过设置 lock_timeout 参数,如果 DDL 命令因为等待锁而阻塞查询超过 2 秒,该命令将会失败。这样做的缺点是 ALTER TABLE 可能不会成功,但可以稍后再试。在开始 DDL 命令之前,建议先查询 pg_stat_activity,查看是否有长时间运行的查询。

3.CONCURRENTLY (并行地)创建索引

PostgreSQL 的另一条黄金法则是:始终并行地创建索引。

在大型数据集上创建索引可能需要数小时甚至数天,而常规的 CREATE INDEX 命令会在命令执行期间阻止所有写入操作。虽然不会阻塞 SELECT,但这仍然很糟糕,而且还有更好的方法:CREATE INDEX CONCURRENTLY。

不要这样做:

-- 阻塞所有写
CREATE INDEX items_value_idx ON items USING GIN (value jsonb_path_ops);

而应这样做:

-- 只阻塞其他 DDL 操作
CREATE INDEX CONCURRENTLY items_value_idx ON items USING GIN (value jsonb_path_ops);

并发地创建索引有一个缺点。如果出了问题,它不会回滚,而是留下一个未完成(invalid)的索引。如果出现这种情况,不用担心,只需运行 DROP INDEX CONCURRENTLY items_value_idx,然后再尝试创建一次即可。

译者按,在 Bytebase 中对应的是这条 SQL 审核规则:

图片图片

4.尽可能晚地获取高级别的锁

当需要运行命令获取表上高级别的锁时,应尽量在事务的较晚阶段执行,以允许查询尽可能长时间地进行。

例如,如果要完全替换表的内容。不要这样做:

BEGIN;
-- 阻塞读写:
TRUNCATE items;
-- 长时间操作:
\COPY items FROM 'newdata.csv' WITH CSV 
COMMIT;

取而代之的是,将数据加载到新表中,然后替换旧表:

BEGIN;
CREATE TABLE items_new (LIKE items INCLUDING ALL);
-- 长时间操作:
\COPY items_new FROM 'newdata.csv' WITH CSV
-- 阻塞读写:
DROP TABLE items;
ALTER TABLE items_new RENAME TO items;
COMMIT;

有一个问题,我们没有从一开始就阻止写入,因此当我们删除旧的 items 表时,它可能已经发生了变化。为了防止出现这种情况,我们可以显式锁表,阻止写入,但不阻止读取:

BEGIN;
LOCK items IN EXCLUSIVE MODE;
...

有时,自己动手加锁更为保险。

5.添加主键并尽量减少加锁

在表中添加主键通常是个好主意。例如,当你想使用逻辑复制迁移数据库时。

Postgres 让使用 ALTER TABLE 创建主键变得非常简单,但在为主键建立索引的过程中(如果表很大,可能需要很长时间),所有查询都会被阻塞。

ALTER TABLE items ADD PRIMARY KEY (id); -- 长时间阻塞查询

幸运的是,你可以先使用 CREATE UNIQUE INDEX CONCURRENTLY 完成所有繁重的工作,然后使用唯一索引作为主键,这是一种快速操作。

CREATE UNIQUE INDEX CONCURRENTLY items_pk ON items (id); -- 会很长,但不会阻塞查询
ALTER TABLE items ADD CONSTRAINT items_pk PRIMARY KEY USING INDEX items_pk;  -- 会阻塞查询,但很快

通过将创建主键分解为两个步骤,几乎不会对用户造成影响。

6.永远不要使用 VACUUM FULL

Postgres 的用户体验有时会让人吃惊。虽然 VACUUM FULL 听起来像是清除数据库灰尘的命令,但更合适的命令应该是:请冻结我的数据库数小时。

PLEASE FREEZE MY DATABASE FOR HOURS;
VACUUM FULL
VACUUM FULL

虽然你应该调整 AUTO VACUUM 设置并使用索引来加快查询速度,但你可能有时需要运行 VACUUM,而不是 VACUUM FULL。

7.通过重排指令避免死锁

如果你已经使用 PostgreSQL 有一段时间了,你很可能见过类似的错误:

ERROR:  deadlock detected
DETAIL:  Process 13661 waits for ShareLock on transaction 45942; blocked by process 13483.
Process 13483 waits for ShareLock on transaction 45937; blocked by process 13661.

当并发事务以不同顺序获取相同锁时,就会出现这种情况。例如,一个事务发出以下命令。

BEGIN;
UPDATE items SET counter = counter + 1 WHERE key = 'hello'; -- 在 hello 上加锁
UPDATE items SET counter = counter + 1 WHERE key = 'world'; -- 一边阻塞 hello,一边等着 world
END;

与此同时,另一个事务可能会发出相同的命令,但顺序不同。

BEGIN
UPDATE items SET counter = counter + 1 WHERE key = 'world'; -- 在 world 上加锁
UPDATE items SET counter = counter + 1 WHERE key = 'hello';  -- 一边阻塞 world,一边等着 hello
END;

如果这些事务同时运行,它们很可能会互相卡住,永远无法完成。Postgres 会在一秒钟左右后识别出这种情况,并取消其中一个事务,让另一个事务完成。出现这种情况时,你应该检查一下自己的应用程序,看看能否让事务始终按照相同的顺序进行。如果两个事务都先修改 hello,再修改 world,那么第一个事务就会在抢到其他锁之前阻塞第二个事务的 hello 锁。

责任编辑:武晓燕 来源: Bytebase
相关推荐

2011-07-05 14:19:02

云备份云计算

2023-02-21 14:55:40

React开发技巧

2010-07-20 10:48:56

Perl文件操作

2013-07-22 15:36:48

谷歌秘密项目

2021-09-17 13:17:56

Spring 模块开发

2018-02-28 10:11:22

UPS电源预防

2009-03-17 08:33:22

Windows7节省开支

2010-05-24 13:04:53

jQueryJavaScript

2021-02-19 23:42:18

高考物联网农业

2018-04-11 14:13:29

物联网信息技术互联网

2024-10-29 08:00:00

PAMPAM部署IT

2014-11-07 16:57:21

程序员

2016-06-06 09:49:15

2024-09-04 08:31:01

语言模型设计

2020-12-22 09:55:55

IT首席信息官CIO

2022-05-23 08:09:42

物联网IOT

2015-07-08 08:51:11

SDN

2020-12-18 10:35:27

IT技术领导者

2009-12-01 14:35:06

Linux忠告

2015-08-05 16:21:23

开源社区
点赞
收藏

51CTO技术栈公众号