
本文创作于 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,为团队安全使用数据库保驾护航。🛡️🎉






文章转载自Bytebase,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。





