解锁 7 大技巧,避免你的 Postgres 挂掉
本文创作于 2018年2月,随着 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);
当心锁队列,使用 lock timeouts (锁超时)
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,查看是否有长时间运行的查询。CONCURRENTLY (并行地)创建索引
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);
DROP INDEX CONCURRENTLY items_value_idx,然后再尝试创建一次即可。尽可能晚地获取高级别的锁
当需要运行命令获取表上高级别的锁时,应尽量在事务的较晚阶段执行,以允许查询尽可能长时间地进行。
BEGIN;-- 阻塞读写:TRUNCATE items;-- 长时间操作:\COPY items FROM 'newdata.csv' WITH CSVCOMMIT;
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;...
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; -- 会阻塞查询,但很快
永远不要使用 VACUUM FULL
VACUUM FULL 听起来像是清除数据库灰尘的命令,但更合适的命令应该是:请冻结我的数据库数小时。PLEASE FREEZE MY DATABASE FOR HOURS;
VACUUM FULL会将整个表重写到磁盘上,这可能需要数小时或数天的时间,并且在重写过程中会阻止所有查询。虽然VACUUM FULL有一些有效的使用情况,例如表以前很大,但现在变小了,仍占用大量空间,但这极可能不是你面临的情况。
VACUUM,而不是 VACUUM FULL。
通过重排指令避免死锁
ERROR: deadlock detectedDETAIL: 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,一边等着 worldEND;
BEGINUPDATE items SET counter = counter + 1 WHERE key = 'world'; -- 在 world 上加锁UPDATE items SET counter = counter + 1 WHERE key = 'hello'; -- 一边阻塞 world,一边等着 helloEND;
hello,再修改 world,那么第一个事务就会在抢到其他锁之前阻塞第二个事务的 hello 锁。译者按,优秀的数据库仍然需要「正确」的使用姿势,但是人工检查总会疏漏。好在 Bytebase 落地了 SQL 审核(并在社区版本中全面开放),将规范强制为约束,自动化检查每一条 SQL,为团队安全使用数据库保驾护航。🛡️🎉