基于PostgreSQL 9.4
四、显式锁定
PostgreSQL提供了多种锁模式用于控制对表中数据的并发访问。这些模式可以用于在MVCC无法给出期望行为的场合。同样,大多数PostgreSQL命令自动施加恰当的锁以保证被引用的表在命令的执行过程中不会以一种不兼容的方式被删除或者修改。 比如,在存在其它并发操作的时候,TRUNCATE是不能在同一个表上面执行的。
要检查数据库服务器里所有当前正在被持有的锁,可以使用pg_locks系统视图。有关监控锁管理器子系统状态的更多信息,请参考章Chapter 27。
3.1表级锁:
下面的列表显示了可用的锁模式和它们被PostgreSQL自动使用的场合。你也可以用LOCK命令明确获取这些锁。两个事务在同一时刻不能在同一个表上持有相互冲突的锁。不过,一个事务决不会和自身冲突。比如,它可以在一个表上请求ACCESS EXCLUSIVE然后接着请求 ACCESS SHARE。 非冲突锁模式可以被许多事务同时持有。请特别注意有些锁模式是自冲突的(比如,在任意时刻ACCESS EXCLUSIVE模式就不能够被多个事务拥有), 但其它锁模式都不是自冲突的(比如,ACCESS SHARE可以被多个事务持有)。
表级锁LOCK TABLE命令语法:
LOCK [ TABLE ] [ ONLY ] name [ * ] [, ...] [ IN lockmode MODE ] [ NOWAIT ] 这里的lockmode可以是下列之一:
ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE
| SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE
下面是可用的锁模式和它们被PostgreSQL自动使用的场合。你也可以用LOCK命令明确获取这些锁。请注意所有这些锁模式都是表级锁,即使它们的名字包含"row"单词(这些名称是历史遗产)。从某种角度而言,这些名字反应了每种锁模式的典型用法—但是语意却都是一样的。两种锁模式之间真正的区别是它们有着不同的冲突锁集合(参见Table 13-2)。 两个事务在同一时刻不能在同一个表上持有相互冲突的锁。不过,一个事务决不会和自身冲突。比如,它可以在一个表上请求ACCESS EXCLUSIVE然后接着请求ACCESS SHARE。非冲突锁模式可以被许多事务同时持有。请特别注意有些锁模式是自冲突的(比如,在任意时刻ACCESS EXCLUSIVE模式就不能够被多个事务拥有),但其它锁模式都不是自冲突的(比如,ACCESS SHARE可以被多个事务持有)。
8大表级锁模式:
ACCESS SHARE:只与ACCESS EXCLUSIVE冲突。 SELECT命令在引用的表上请求这个锁。通常,任何只读取表而不修改它的命令都请求这种锁模式。
--事务1
BEGIN;
--给表lyy上access share锁
LOCK TABLE lyy IN ACCESS SHAER MODE;
--与SELECT * FROM lyy;产生相同类型的锁
--事务2:
BGEIN;
--给表lyy上access exclusive锁.
LOCK TABLE lyy IN ACCESS EXCLUSIVE MODE;
--与DROP TABLE lyy或者其他DDL操作产生相同类型的锁。
--以上操作被阻塞,因为产生了锁冲突
ROW SHARE:与EXCLUSIVE和ACCESS EXCLUSIVE锁模式冲突。SELECT FOR UPDATE和SELECT FOR SHARE命令在目标表上需要一个这样模式的锁 (加上在所有被引用但没有ACCESS SHARE的表上的FOR UPDATE/FOR SHARE锁)。
ROW EXCLUSIVE:与SHARE,SHARE ROW EXCLUSIVE,EXCLUSIVE和ACCESS EXCLUSIVE锁模式冲突。UPDATE,DELETE和INSERT命令自动请求这个锁模式(加上所有其它被引用的表上的ACCESS SHARE锁)。通常,这种锁将被任何修改表中数据的查询请求。
SHARE UPDATE EXCLUSIVE:与SHARE UPDATE EXCLUSIVE,SHARE,SHARE ROW EXCLUSIVE,EXCLUSIVE和ACCESS EXCLUSIVE锁模式冲突。这个模式保护一个表不被并发模式改变和VACUUM。 VACUUM(不带FULL选项),ANALYZE,CREATE INDEX CONCURRENTLY和ALTER TABLE请求这样的锁。
SHARE:与ROW EXCLUSIVE,SHARE UPDATE EXCLUSIVE,SHARE ROW EXCLUSIVE,EXCLUSIVE和ACCESS EXCLUSIVE锁模式冲突。这个模式避免表的并发数据修改。CREATE INDEX(不带CONCURRENTLY选项)语句要求这样的锁模式。
SHARE ROW EXCLUSIVE:与ROW EXCLUSIVE,SHARE UPDATE EXCLUSIVE,SHARE,SHARE ROW EXCLUSIVE,EXCLUSIVE和ACCESS EXCLUSIVE锁模式冲突。 这个模式避免表的并发数据修改。 并且是自我排斥的,因此每次只有一个会话可以拥有它。 任何PostgreSQL命令都不会自动请求这个锁模式。
EXCLUSIVE:与ROW SHARE,ROW EXCLUSIVE,SHARE UPDATE EXCLUSIVE,SHARE,SHARE ROW EXCLUSIVE,EXCLUSIVE和ACCESS EXCLUSIVE锁模式冲突。这个模式只允许并发ACCESS SHARE锁,也就是说,只有对表的读动作可以和持有这个锁模式的事务并发执行。 任何PostgreSQL命令都不会在用户表上自动请求这个锁模式。
ACCESS EXCLUSIVE:与所有模式冲突(8大模式,也包括自身类型)。这个模式保证其所有者(事务)是可以访问该表的唯一事务。 ALTER TABLE,DROP TABLE,TRUNCATE,REINDEX,CLUSTER和VACUUM FULL命令要求这样的锁。多种形式的ALTER TABLE也要求一个该级别的锁 (参见ALTER TABLE).在LOCK TABLE命令没有明确声明需要的锁模式时,ACCESS EXCLUSIVE是缺省表锁模式。
注意:只有ACCESS EXCLUSIVE阻塞SELECT(不包含FOR UPDATE/SHARE语句)。
--事务1
BEGIN;
LOCK TABLE lyy in access exclusive mode;
--事务2
BEGIN;
SELECT * FROM lyy;
--与LOCK TABLE lyy IN ACCESS SHAER MODE;都产生ACCESS SHARE锁
--SELECT操作阻塞,因为与access exclusive产生了锁冲突
一旦请求已获得某种锁,那么该锁模式将持续到事务结束。但是如果在建立保存点之后才获得锁,那么在回滚到这个保存点的时候将立即释放所有该保存点之后获得的锁。这与ROLLBACK取消所有保存点之后对表的影响的原则一致。同样的原则也适用于PL/pgSQL异常块中获得的锁:一个跳出块的错误将释放在块中获得的锁。
--事务1
postgres=# begin;
BEGIN
postgres=# LOCK TABLE lyy IN ACCESS SHARE MODE;
LOCK TABLE
postgres=# savepoint svp1;
SAVEPOINT
postgres=# LOCK TABLE lyy IN ACCESS exclusive MODE;
LOCK TABLE
--事务2
postgres=# begin;
BEGIN
postgres=# select * from lyy;--此时操作被阻塞,因为此时是ACCESS exclusive锁
--事务1
postgres=# rollback to savepoint svp1;//此时是access share锁
ROLLBACK
--事务2
此时select * from lyy返回查询结果.//access share锁不阻塞SELECT操作。
冲突锁模式对比表
Requested Lock Mode
Current Lock Mode
ACCESS SHARE
ROW SHARE
ROW EXCLUSIVE
SHARE UPDATE EXCLUSIVE
SHARE
SHARE ROW EXCLUSIVE
EXCLUSIVE
ACCESS EXCLUSIVE
ACCESS SHARE
X
ROW SHARE
X
X
ROW EXCLUSIVE
X
X
X
X
SHARE UPDATE EXCLUSIVE
X
X
X
X
X
SHARE
X
X
X
X
X
SHARE ROW EXCLUSIVE
X
X
X
X
X
X
EXCLUSIVE
X
X
X
X
X
X
X
ACCESS EXCLUSIVE
X
X
X
X
X
X
X
X
备注: 上图是Postgresql 表级锁的各种冲突模式对照表,‘X’表示冲突项。
3.2行级锁:
行级锁可以是排他的或者是共享的。特定行上的排他行级锁是在行被更新的时候自动请求的。 该锁一直保持到事务提交或者回滚。行级锁不影响对数据的查询,它们只阻塞对同一行的写入。
行级锁(SELECT .. FOR ..)命令语法
FOR UPDATE,FOR NO KEY UPDATE,FOR SHARE和FOR KEY SHARE是锁定子句;他们影响SELECT如何从表中锁定行作为获得的行。锁定子句的一般形式:
FOR lock_strength [ OF table_name [, ...] ] [ NOWAIT ]
这里的lock_strength可以是下列之一:
UPDATE
NO KEY UPDATE
SHARE
KEY SHARE
4个行级锁强度
FOR UPDATE:令那些被SELECT检索出来的行被锁住,就像在更新一样。这样就避免它们在当前事务结束前被其它事务修改或者删除;也就是说, 其它企图UPDATE,DELETE,SELECT FOR UPDATE,SELECT FOR NO KEY UPDATE,SELECT FOR SHARE或SELECT FOR KEY SHARE这些行的事务将被阻塞,直到当前事务结束。同样, 如果一个来自其它事务的UPDATE,DELETE,SELECT FOR UPDATE已经锁住了某个或某些选定的行,SELECT FOR UPDATE将等到那些事务结束,并且将随后锁住并返回更新的行(或者不返回行,如果行已经被删除)。但是,在REPEATABLE READ或SERIALIZABLE事务内部,如果在事务开始时要被锁定的行已经改变了,那么将抛出一个错误。更多的讨论参阅Chapter 13。
FOR NO KEY UPDATE的行为类似于FOR UPDATE,只是获得的锁比较弱:该锁不阻塞尝试在相同的行上获得锁的SELECT FOR KEY SHARE命令。该锁模式也可以通过任何不争取FOR UPDATE锁的UPDATE获得。
FOR SHARE的行为类似于FOR NO KEY UPDATE,只是它在每个检索出来的行上获得一个共享锁,而不是一个排它锁。一个共享锁阻塞其它事务在这些行上执行UPDATE,DELETE,SELECT FOR UPDATE或SELECT FOR NO KEY UPDATE,却不阻止他们执行SELECT FOR SHARE或SELECT FOR KEY SHARE。
FOR KEY SHARE的行为类似于FOR SHARE,只是获得的锁比较弱: 阻塞SELECT FOR UPDATE但不阻塞SELECT FOR NO KEY UPDATE。一个共享锁阻塞其他事务执行DELETE或任意改变键值的UPDATE, 但是不阻塞其他UPDATE,也不阻止SELECT FOR NO KEY UPDATE, SELECT FOR SHARE 或SELECT FOR KEY SHARE。
为了避免操作等待其它事务提交,使用NOWAIT选项。如果被选择的行不能立即被锁住, 那么语句将会立即汇报一个错误,而不是等待。请注意,NOWAIT只适用于行级别的锁, 要求的表级锁ROW SHARE仍然以通常的方法进行(参阅Chapter 13)。 如果需要申请表级别的锁同时又不等待,那么你可以使用LOCK的NOWAIT选项。
举例说明:
准备表和数据:
CREATE TABLE lyy(id int primary key, name varchar);
INSERT INTO lyy values(1,'a1'),(2,'a2'),(3,'a3'),(4,'a4');
--for update
事务1:
BEGIN;
SELECT * FROM lyy WHERE id<3 FOR UPDATE;
事务2:
BEGIN;
UPDATE lyy SET name='aa' WHERE id=2;
--此时id=2的行已被FOR UPDATE锁定,所以update操作阻塞,直到事务1,提交之后,才能执行。
--for no key update
事务1:
BEGIN;
SELECT * FROM lyy WHERE id=1 FOR NO KEY UPDATE;
--在对行1的锁定效果上,等价于
update lyy set id=2 where id=2;
事务1:
BEGIN;
SELECT * FROM lyy WHERE id=1 FOR KEY SHARE;
--该行可以执行,因为虽然id=1的行已被锁定,但是FOR NO KEY UPDATE不阻塞SELECT ... FOR KEY SHARE;
--for share
事务1:
BGEIN;
SELECT * FROM lyy WHERE id=1 FOR SHARE;
事务2:
BEGIN;
SELECT * FROM lyy WHERE id=1 FOR [KEY] SHARE;--可以执行,因为该行的FOR SHARE锁,不阻塞FOR [KEY] SHARE;
UPDATE lyy set name='sss' WHERE id=1; --阻塞,
--for key share
事务1:
BGEIN;
SELECT * FROM lyy WHERE id=1 FOR KEY SHARE;
事务2:
BEGIN;
UPDATE lyy SET id=222 WHERE id=2; --阻塞,for key share 阻塞键值字段的update操作;
UPDATE lyy SET name='coco' WHERE id=2; --正常执行,for update share不阻塞非键值字段的update操作。
3.3页级锁
除了表级锁和行级锁,页级别的共享/排他锁用来控制中对表页的读/写访问(在共享缓存池)。一个行被抓去或者更新后,这些锁会迅速被释放。应用开发者通常不需要关心页级锁,在此处提及页级锁是为了锁机制介绍的完整性。
3.4死锁:
显式锁定的使用可能会增加死锁的可能性。
死锁是指两个(或两个以上)事务相互持有对方期待的锁,如果没有其他机制,这些事务都将无法进行下去。
避免死锁的方法
防止死锁的最好方法通常是保持所有使用同一个数据库的应用都能与相同的顺序在多个对象上请求排它锁。
由于数据库可以自动检测出死锁,所以应用也可以通过补货死锁异常来处理思索。但这不是一个很好的方法,因为数据库检测死锁需要一些代价,可能会导致应用程序过久持有排它锁,从而导致系统的并发处理能力下降。
排它锁持有的时间越长,就越容易导致死锁,所以在程序设计时,要尽量短的持有排它锁。
阻塞与死锁的区别
数据库阻塞的现象: 第一个连接占有资源没有释放,而第二个连接需要获取这个资源。如果第一个连接没有提交或者回滚,第二个连接会一直等待下去,直到第一个连接释放该资源为止。对于阻塞,数据库无法处理,所以对数据库操作要及时地提交或者回滚。
数据库死锁的现象:第一个连接占有资源没有释放,准备获取第二个连接所占用的资源,而第二个连接占有资源没有释放,准备获取第一个连接所占用的资源。这种互相占有对方需要获取的资源的现象叫做死锁。对于死锁,数据库处理方法:牺牲一个连接,保证另外一个连接成功执行。
3.5咨询锁:
PostgreSQL允许创建由应用定义其含义的锁。这种锁被称为咨询锁, 因为系统并不强迫其使用— 而是由应用来保证其被恰当的使用。 咨询锁可用于 MVCC 难以实现的锁定策略。 比如,咨询锁一般用于模拟常见于"平面文件"数据管理系统的悲观锁策略。 虽然可以用存储在表中的一个特定标志达到同样的目的,但是使用咨询锁更快,还可以避免表臃肿, 更可以在会话结束的时候由系统自动执行清理工作。