你好! 下周二,
“关系数据库管理系统”课程将重新开始学习,因此我们将继续发布有关该主题的有用材料。 走吧

上周,我写了关于
Postgres中竞争性访问的信息 ,哪些团队互相阻塞,以及如何诊断阻塞的团队。 当然,在诊断后,您可能需要治疗。 使用Postgres,您可以用脚射击自己,但是Postgres还为您提供了避免误解的方法。 以下是一些有关如何操作和如何不执行操作的重要提示,当与用户合作时,从他们的单个Postgres数据库迁移到
Citus或
实时创建新的
分析应用程序时,这些提示非常有用。
1.切勿添加具有默认值的列
PostgreSQL的黄金法则:在生产环境中向表中添加列时,
切勿指定默认值 。
添加列需要非常积极的表锁定,该锁定会阻止读取和写入。 如果您添加具有默认值的列,PostgreSQL将覆盖整个表以为每一行填写默认值,在大表中可能要花费几个小时。 同时,所有请求都将被阻止,因此您的数据库将不可用。
不要这样做:
这样做得更好:
或者,甚至更好的是,避免长时间更新和
delete
锁,而要进行小批量更新,例如:
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);
这样,您可以在对用户造成最小干扰的情况下添加和填充新列。
2.当心锁队列,使用超时
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.使用非阻塞索引创建
PostgreSQL的另一个黄金法则:始终使用非阻塞索引创建。
为大型数据集创建索引可能需要数小时甚至数天,而常规的
CREATE INDEX
命令会在命令期间锁定所有记录。 尽管它不会阻止SELECT的事实,但它仍然很糟糕,还有一种更好的方法:
CREATE INDEX CONCURRENTLY
。
不要这样做:
相反,请执行以下操作:
非阻塞索引的创建有一个缺点。 如果出现问题,它不会回滚并留下不完整的(“无效”)索引。 如果发生这种情况,不用担心,只需运行
DROP INDEX CONCURRENTLY items_value_idx
并尝试再次创建它。
4.尽早使用主动锁
当您需要运行接收主动表锁的命令时,请尝试在事务中尽可能晚地执行此操作,以使查询可以尽可能长地继续。
例如,如果您想完全替换表的内容。 不要这样做:
BEGIN;
而是将数据加载到新表中,然后替换旧表:
BEGIN; CREATE TABLE items_new (LIKE items INCLUDING ALL);
有一个问题:我们从一开始就没有阻止记录,并且在重新设置元素时旧的元素表可能已经更改。 为了防止这种情况,我们可以显式锁定表以进行写入,但不能进行读取:
BEGIN; LOCK items IN EXCLUSIVE MODE; ...
有时候,最好将障碍物掌握在自己手中。
5.添加具有最小阻塞的主键
向表中添加主键通常是一个好主意。 例如,如果要使用逻辑复制或使用
Citus Warp迁移数据库。
Postgres使得使用
ALTER TABLE
创建主键非常容易,但是现在为主键创建一个索引,如果表很大,可能会花费很多时间,所有请求都将被阻止。
ALTER TABLE items ADD PRIMARY KEY (id);
幸运的是,您可以首先使用
CREATE UNIQUE INDEX CONCURRENTLY
进行所有艰苦的工作,然后使用唯一索引作为主键,这是一项快速的操作。
CREATE UNIQUE INDEX CONCURRENTLY items_pk ON items (id);
将主键的创建分为两个阶段实际上并不影响用户。
6.切勿使用VACUUM FULL
用户体验postgres有时可能会很棒。 尽管
VACUUM FULL
听起来像您要清理数据库的“灰尘”,但是更合适的命令是:
PLEASE FREEZE MY DATABASE FOR HOURS;
VACUUM FULL
将整个表覆盖到磁盘,这可能需要数小时或数天,并且同时会阻止所有请求。 尽管
VACUUM FULL
有几种有效的用
VACUUM FULL
,例如一个曾经很大的表,但是现在它很小,仍然占用很多空间,但这可能不是您的选择。
尽管您应该努力配置自动清除选项并使用索引来加快查询速度,但是有时您可以运行
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';
同时,另一个事务可以发出相同的命令,但是顺序不同。
BEGIN UPDATE items SET counter = counter + 1 WHERE key = 'world';
如果这些事务块同时执行,则它们很可能会卡在彼此之间等待,并且永远不会结束。 Postgres大约在一秒钟内就会意识到这种情况,并将取消其中一项交易以完成另一项交易。 发生这种情况时,应查看您的应用程序,以了解是否可以确保始终以相同的顺序执行事务。 如果两个事务都先更改为
hello
,然后更改
world
,则第一个事务将在您可以捕获任何其他锁之前将第二个锁定为
hello
。
分享您的提示!
我们希望这些建议对您有所帮助。 如果您还有其他建议,请随时在
Slack上发推特
@citusdata或我们活跃的
Citus用户社区。
我们提醒您,在几个小时内会有一个
开放日 ,我们将详细讨论即将开设的课程的计划。