乐观锁和悲观锁(不太有用)

         乐观锁和悲观锁

分类: 数据库架构 
为什么需要锁(并发控制)?

在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。

典型的冲突有:

丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新。

脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6。

为了解决这些并发带来的问题。 我们需要引入并发控制机制。

并发控制机制

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。[1]

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。

乐观锁应用

1.      使用自增长的整数表示数据版本号。更新时检查版本号是否一致,比如数据库中数据版本为6,更新提交时version=6+1,使用该version值(=7)与数据库version+1(=7)作比较,如果相等,则可以更新,如果不等则有可能其他程序已更新该记录,所以返回错误。

2.      使用时间戳来实现.

注:对于以上两种方式,Hibernate自带实现方式:在使用乐观锁的字段前加annotation: @Version, Hibernate在更新时自动校验该字段。

悲观锁应用

需要使用数据库的锁机制,比如SQL SERVER 的TABLOCKX(排它表锁) 此选项被选中时,SQL  Server  将在整个表上置排它锁直至该命令或事务结束。这将防止其他进程读取或修改表中的数据。

SqlServer中使用

Begin Tran
select top 1 @TrainNo=T_NO
         from Train_ticket   with (UPDLOCK)   where S_Flag=0

      update Train_ticket
         set T_Name=user,
             T_Time=getdate(),
             S_Flag=1
         where 
T_NO=@TrainNo
commit

我们在查询的时候使用了with (UPDLOCK)选项,在查询记录的时候我们就对记录加上了更新锁,表示我们即将对此记录进行更新. 注意更新锁和共享锁是不冲突的,也就是其他用户还可以查询此表的内容,但是和更新锁和排它锁是冲突的.所以其他的更新用户就会阻塞.

结论

在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法.

### C++ 中乐观锁悲观锁的区别 在多线程环境中,为了确保数据的一致性完整性,C++ 提供了多种同步机制。其中最常用的两种是乐观锁悲观锁。 #### 悲观锁 (Pessimistic Lock) 悲观锁假设冲突总是会发生,因此在每次访问共享资源之前都会先加锁。这种方式能够有效防止多个线程同时修改同一份数据,但是也带来了性能上的开销,因为即使存在实际的竞争情况也会阻塞其他线程的操作[^2]。 具体来说,在 C++ 中可以通过 `std::mutex` 或者更高级别的读写锁 (`std::shared_mutex`) 来实现悲观锁: ```cpp #include <iostream> #include <thread> #include <mutex> class PessimisticLockExample { private: int value; std::mutex mtx; public: void update(int new_value) { // 加锁前允许任何其它线程进入临界区 std::lock_guard<std::mutex> lock(mtx); this->value = new_value; // 修改受保护的数据成员 } }; ``` #### 乐观锁 (Optimistic Lock) 相比之下,乐观锁则认为大多数情况下不会发生冲突,所以在尝试更新时并不会立即锁定资源。只有当检测到确实发生了并发更改才会采取措施回滚或重试整个事务过程。这种策略对于那些高并发但低争用场景特别有用,因为它减少了必要的等待时间并提高了吞吐量[^3]。 利用原子变量(`std::atomic<T>`), 可以很容易地构建一个简单的乐观锁例子: ```cpp #include <iostream> #include <thread> #include <atomic> class OptimisticLockExample { private: std::atomic<int> counter{0}; public: bool increment() noexcept { auto expected = counter.load(); while (!counter.compare_exchange_weak(expected, expected + 1)) { // 如果比较交换失败,则重新加载最新值继续循环直到成功为止 expected = counter.load(); } return true; } }; ``` 上述代码展示了如何通过 CAS(Compare And Swap)指令来完成一次无锁的计数器增加操作。这里的关键在于 `compare_exchange_weak()` 函数调用——它会检查当前存储的值是否等于预期值;如果是的话就将其替换为新值,并返回真表示成功;否则保持原样变并将旧值赋给第一个参数以便下次再试。 ### 性能考量 选择哪种类型的锁取决于应用程序的具体需求。如果程序中的大部分工作负载都是只读查询而很少有写入动作,那么使用读写锁可能是一个错的选择,它可以允许多个读者同时访问而必担心持有独占锁带来的延迟问题。然而,如果有大量的写请求存在,则应该考虑采用更加轻量级的方法比如基于版本号控制或是 TAOCP 描述的那种细粒度的对象内建自旋锁等技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值