在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。
悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。
一、理解悲观锁与乐观锁
获取锁的前提:结果集中的数据没有使用排他锁或共享锁时,才能获取锁,否则将会阻塞。
需要注意的是, FOR UPDATE
生效需要同时满足两个条件时才生效:
二、理解悲观锁与乐观锁
在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和一致性以及数据库的一致性。
乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有类似的概念。
针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库本身提供的锁机制来实现的。
三、悲观锁的流程
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。
如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。
在oracle中,利用 select * for update 可以锁表。假设有个表单products ,里面有id跟name二个栏位,id是主键。
案例说明
1 明确指定主键,并且查询的数据存在,行锁(row lock)
SELECT * FROM products WHERE id='3' FOR UPDATE;
2 明确指定主键,并且查询的数据 不存在,无锁
SELECT * FROM products WHERE id='-1' FOR UPDATE;
3 无主键,表锁(table lock)
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
4 主键不明确,表锁(table lock)
SELECT * FROM products WHERE id<>'3' FOR UPDATE;
5 主键不明确,表锁(table lock)
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;
注意:FOR UPDATE仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效。
测试说明
使用悲观锁的原理就是,在查询出 pay_order 信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为 pay_order 被锁定了,就不会出现其他操作者来对其进行修改了。
1、不开启事务,明确指定主键,且该数据存在
可以看出如果不在事务中,则查询加锁是无效的。
2、开启事务,明确指定主键,且该数据存在。提交和不提交事务
3、开启事务,明确指定主键,但该数据不存在
说明:窗口1 查询结果为空。窗口2 查询结果也为空,查询无阻塞,说明 窗口1 没有对数据执行锁定。
4、开启事务,不指定主键,且该数据存在
5、开启事务,主键不明确,则会表锁(table rock)
四、小结
当执行 select ... for update时,将会把数据锁住,因此,我们需要注意一下锁的级别。MySQL InnoDB 默认为行级锁。当查询语句指定了主键时,MySQL会执行「行级锁」,否则MySQL会执行「表锁」。
常见情况如下:
innoDB的行锁是通过给索引上的索引项加锁实现的,因此,只有通过索引检索数据,才会采用行锁,否则使用的是表锁。
五、总结
悲观锁采用的是「先获取锁再访问」的策略,来保障数据的安全。但是加锁策略,依赖数据库实现,会增加数据库的负担,且会增加死锁的发生几率。
此外,对于不会发生变化的只读数据,加锁只会增加额外不必要的负担。在实际的实践中,对于并发很高的场景并不会使用悲观锁,因为当一个事务锁住了数据,那么其他事务都会发生阻塞,会导致大量的事务发生积压拖垮整个系统。