出色的咨询锁及其居住地

PostgreSQL有一个非常方便的咨询锁机制,它们也是咨询锁我们Tensor在系统中的许多地方都使用它们,但是很少有人详细了解它们的工作原理以及如果处理不当会出现什么问题



阅读有关锁的更多信息

推荐锁的优点


这种机制与“普通”表/页/记录级别之间的根本区别在于,有几个关键功能。

自定义ID锁


PG中的“普通”锁始终绑定到特定的数据库对象(表,记录,数据页)以及为连接服务的进程。咨询锁也是一个过程,但是可以将抽象标识符设置为(bigint)(integer,integer)来代替真实对象

顺便说一句,将每个锁附加到一个进程意味着通过pg_terminate_backend(pid)在客户端上命名该锁或正确完成从客户端的连接,您可以摆脱它所施加的所有锁。

CAS检查锁捕获


CAS是一个“ 比较并设置”,即,对捕获能力的验证和对锁本身的捕获是作为一个原子操作进行的,显然,没有人可以在它们之间“楔入”。

也就是说,如果您首先向pg_locks发出验证请求,查看结果,然后决定是否锁定,则没有人保证在这些操作之间没有人会设法获取您需要的对象。但是,如果您使用pg_try_advisory_lock,那么您将立即收到此锁,或者该函数将简单地返回FALSE

没有例外和期望就没有捕获


模型中存在“普通”锁。“如果您已经请求了锁,请等待。如果你不想等待(NOWAITstatement_timeoutlock_timeout) -这里是一个例外“。这种方法极大地干扰了事务,因为这时您必须实现一个BEGIN-EXCEPTION-END处理或回滚ROLLBACK事务。

避免此行为的唯一方法是使用SELECT ... SKIP LOCKED9.5版随附的设计。不幸的是,使用这种方法,“根本没有要阻止的内容”和“曾经但已经被阻止”的选项变得难以区分。

try函数引起的建议锁定仅会返回TRUE/FALSE
请勿混淆,pg_advisory_lock并且-第一个函数将等待直到收到锁为止,如果无法“立即”捕获第二个函数,则第二个函数立即立即返回pg_try_advisory_lockFALSE

交易内和交易后锁定


如前所述,对象锁是“附加”到进程的,仅作为当前事务的一部分存在。即使只是强加-也不会成功:

LOCK TABLE tbl;
-- ERROR:  LOCK TABLE can only be used in transaction blocks

因此,在交易结束时,所有强加给它的锁都将被删除。相比之下,咨询锁最初设计为能够持有锁并且不在事务范围之内

SELECT pg_advisory_lock(1);
SELECT * FROM pg_locks WHERE pid = pg_backend_pid() AND locktype = 'advisory';

-[ RECORD 1 ]------+--------------
locktype           | advisory
database           | 263911484
relation           |
page               |
tuple              |
virtualxid         |
transactionid      |
classid            | 0 <--  int4 #1    int8
objid              | 1 <--  int4 #2    int8
objsubid           | 1
virtualtransaction | 416/475768
pid                | 29264
mode               | ExclusiveLock
granted            | t
fastpath           | f

但是,从9.1 版开始,已经出现xact版本的咨询功能,使您可以实现“普通”锁的行为,这些锁将在强加普通锁的事务完成时自动释放。

在VLSI中的使用示例


实际上,咨询与其他任何锁一样,可以确保处理资源的唯一性。在我们国家,此类资源通常是整个表或表的特定记录,出于某种原因,它们不希望被“锁定”。

工人的单一处理


如果需要处理数据库中的某些数据是由外部事件触发的,但是多处理是多余的或可能导致争用情况,则合理的做法是一次处理一个处理此数据的进程

为此,我们将尝试使用表标识符作为第一个参数,并将特定应用程序处理ID作为第二个参数进行锁定:

SELECT pg_try_advisory_lock(
  'processed_table'::regclass::oid
, -1 --   worker'
);

如果我们返回FALSE,则说明其他人已经持有了这种锁,特别是此过程不需要执行任何操作,但是最好安静地结束。例如,这就是对每个客户方案进行隔离的动态计算仓库中货物成本的过程的工作方式。

并发队列处理


现在任务是“相反的”-我们希望尽快,多线程,容错甚至甚至来自不同的业务逻辑处理某些队列表中任务(好吧,我们缺乏一个的功能)-例如,就像我们的操作员那样电子报告向政府机构或OFD服务转移

由于“处理中”的BL是不同的服务器,因此不再有互斥体可以挂断。挑出一些特殊的任务分配流程协调员“杀死”他是不安全的,否则一切都会增加。因此,事实证明,直接在数据库级别分配任务是最有效的,并且有一种方法-在古代,Dmitry Koterov诚实地监视了该模型,然后进行了创造性的修改。

在这种情况下,我们将锁定特定记录的表ID和PK:

SELECT
  *
FROM
  queue_table
WHERE
  pg_try_advisory_lock('queue_table'::regclass::oid, pk_id)
ORDER BY
  pk_id
LIMIT 1;

也就是说,该过程将从表中接收尚未被其竞争兄弟阻塞的第一条记录。

但是,如果PK不是由(integer)而是由(integer,integer)组成(例如,在相同的成本计算中),则可以直接在此对上施加锁-不太可能与“竞争者”发生交叉。

重要!不要忘记定期正确维护您的队列表

独家文件处理


它在文档管理解决方案中无处不在实际上,在分布式Web系统中,可以同时打开一个和相同的文档以供不同的用户查看,但是只能在一个时间任意处理(更改其状态等)。

传统问题


没有他们的地方!几乎所有人都归结为一件事他们没有解锁他们锁定的东西

一个咨询锁的多重覆盖


RTFM,正如他们所说:
如果一次收到几个阻塞请求,它们就会累积起来,因此,如果一个资源被阻塞了三次,则需要将其解除阻塞三次才能在其他会话中可用。

一次放太多锁




成千上万!再次阅读手册
咨询锁和常规锁都存储在共享存储区中,共享存储区的大小由配置参数max_locks_per_transaction和决定max_connections此内存足够重要,因为否则服务器将无法发出任何因此,取决于服务器配置,服务器可以发出的建议锁定的数量通常限制为数以万计。

通常,如果您想强加几千个咨询锁(即使稍后再正确删除它们)时会出现这种情况,请认真考虑当服务器启动时将在哪里运行。

过滤记录时泄漏


在这里,我们接受先前的请求,并添加无害条件,例如奇偶校验ID- AND pk_id % 2 = 0每个条目的两个条件将被检查结果,它pg_try_advisory_lock完成了,锁被叠加了,然后按奇偶校验过滤记录



或手册中的一个选项:
SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- !

仅此而已- 阻塞仍然存在,但是我们还没有意识到。在最坏的情况下-使用正确的请求来处理它pg_advisory_unlock_all

哦,很困惑!


该流派的经典作品...

混淆了,并想知道它为什么能长期工作。但是因为非try版本正在等待混淆,并想知道锁的去向-并在事务中“终止”。事务由一个请求组成,因为没有地方“明确”声明,是的。pg_try_advisory_lockpg_advisory_lock

pg_try_advisory_lockpg_try_advisory_xact_lock

通过pgbouncer工作


当出于性能考虑,使用数据库在事务模式下通过pgbouncer进行处理时,这是许多痛苦的单独来源

这意味着,在数据库的同一连接(实际上通过pgbouncer)上运行的两个相邻事务可以在数据库侧的不同“物理”连接中执行而且它们有自己的锁。每个锁都有



一些选择:

  • 或通过直接连接到数据库来工作
  • 或发明一种算法,以便所有咨询锁仅在事务内(xact)

目前为止就这样了。

All Articles