一、序
在本月的面试中,面试官就问到了:在秒杀系统中,为什么可以使用乐观锁解决超卖问题而不能使用悲观锁呢?
二、锁
- 数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。
- 乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
三、悲观锁(悲观并发控制)
- 悲观锁(Pessimistic Concurrency Control,缩写”PCC”),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程,和Python中GIL锁一样)
- 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
- 悲观锁:主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。
四、乐观锁(乐观并发控制)
- 乐观锁(Optimistic Concurrency Control,缩写”OCC”),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
- 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
- 乐观锁:多数用于数据争用不大、冲突较少的环境中,这种环境中,偶尔回滚事务的成本会低于读取数据时锁定数据的成本,因此可以获得比其他并发控制方法更高的吞吐量。
- 相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。
五、悲观锁的不足
- 悲观并发控制实际上是”先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数。
- 也就是悲观锁的不足,在实际项目中,悲观锁类似于我们在多线程资源竞争时添加的互斥锁,容易出现死锁现象,采用不多。
六、补充 - 数据版本记录机制
- 在上述说到,一般的实现乐观锁的方式就是记录数据版本。数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。
- 实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳(timestamp)。
1. 版本号
- 一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
# 1.查询出商品信息
select (status,status,version) from t_goods where id=id
# 2.根据商品信息生成订单
# 3.修改商品status为2
update t_goods
set status=2,version=version+1
where id=id and version=version;
2. 时间戳
- 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

本文总结了广州Python面试中的悲观锁与乐观锁知识点。悲观锁在并发控制中采取保守策略,每次访问数据时都会上锁,可能导致死锁和额外开销;而乐观锁假设不会发生并发冲突,仅在提交时检查版本号或时间戳,适用于读多写少的场景,以提高吞吐量。文章详细介绍了两者的概念、应用场合和不足,并补充了数据版本记录机制的实现方式。

2318

被折叠的 条评论
为什么被折叠?



