锁模式决定谁需要等待

锁不一定把一切访问都挡住。数据库为资源规定不同模式及兼容关系,只有相互冲突的请求才必须等待或失败;表级锁和行级锁保护的范围也不同。

例如PostgreSQL的普通SELECT会取得相应表级访问锁,仍可与许多读写并行。SELECT FOR UPDATE则锁住选中的行,阻止其他事务对它们执行不兼容修改,但不等于所有普通快照读取都被阻塞。

不等待也有两种不同含义。PostgreSQL的NOWAIT在选中行无法立即加锁时报错,SKIP LOCKED则跳过这些行,常用于多个消费者领取队列表中的不同任务。被锁住的行并没有消失,因此跳过后的结果不是适用于一般查询的一致全貌;它也不证明队列已经为空。这两个选项只改变相应行锁的行为,语句仍须按通常方式取得表级锁,不能把它们理解为整个查询绝不等待。

参考:[1] [3]

等待可以形成循环

事务甲先锁资源A再等B,事务乙先锁B再等A,就可能形成死锁。PostgreSQL检测到这类循环后会中止其中一个事务,让其他事务继续;应用不能依赖固定哪一个被选中。

让所有参与者按一致次序取得资源,可以消除相应的反向等待情形。缩短事务持锁时间也有助于减少竞争;单纯增加等待时长,却不能解开双方都没有机会释放所需资源的循环。

参考:[1]

生命周期也是契约

事务相关的行锁通常随事务结束而释放。PostgreSQL还提供咨询锁,由应用赋予资源含义;其他代码只有遵守同一约定,才会参与这种保护。

咨询锁又区分事务级和会话级。会话级锁不会因为取得它的事务回滚就自动消失,因此“用了锁”仍不足以判断释放时刻。锁的具体模式和生命周期必须与连接管理、异常处理及重试一起考虑。

参考:[1] [2]