在上一个章节5分钟带你读懂事务隔离性与隔离级别 的最后,其实咱们已经提到了锁的概念。本章节接下来将主要介绍如下数据库悲观锁与乐观锁
的相关知识。若有错误还请你们及时指出~mysql
问题:github
在并发环境下,若是多个客户端访问同一条数据,此时就会产生数据不一致的问题,如何解决,经过加锁的机制,常见的有两种锁,乐观锁和悲观锁,能够在必定程度上解决并发访问。sql
百度百科:数据库
悲观锁,正如其名,具备强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)。编程
其余知识点bash
悲观锁主要是共享锁
或排他锁
共享锁
又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据能够共享一把锁,都能访问到数据,可是只能读不能修改。 排他锁
又称为写锁,简称X锁,顾名思义,排他锁就是不能与其余所并存,如一个事务获取了一个数据行的排他锁,其余事务就不能再获取该行的其余锁,包括共享锁和排他锁,可是获取排他锁的事务是能够对数据就行读取和修改。微信
使用场景举例:以MySQL InnoDB为例并发
做为演示,咱们继续使用以前的数据库表:product表学习
productId | productName | productPrice | productCount |
---|---|---|---|
1 | 小米 | 1999 | 100 |
2 | 魅族 | 1999 | 100 |
首先咱们须要set autocommit=0
,即不容许自动提交
有看过上一篇文章5分钟带你读懂事务隔离性与隔离级别 的同窗,能够看到最后咱们使用事务隔离级别时,所引伸出来的根本问题就是能够经过锁机制解决。
在并发状况下回致使数据一致性的问题: 若是有A、B两个用户须要抢productId =1的小米手机,A、B用户都查询小米手机数量是100,A购买后修改商品的数量为99,B购买后修改数量为99。
每次获取小米手机时,对该商品加排他锁。也就是在用户A获取获取 id=1 的小米手机信息时对该行记录加锁,期间其余用户阻塞等待访问该记录。代码以下:
start transaction;
select p.productCount from product p where p.productId = 1 for update;
update product p set p.productCount=p.productCount-1 where p.productId=1 ;
commit;
复制代码
操做
下面同时打开两个窗口模拟2个用户并发访问数据库
时间轴 | 事务A | 事务B |
---|---|---|
T1 | start transaction; | |
T2 | select p.productCount from product p where p.productId = 1 for update; | |
T3 | start transaction; | |
T4 | select p.productCount from product p where p.productId = 1 for update;(等待中...) |
流程说明
select…for update
的方式,这样就经过数据库实现了悲观锁。在这个update事务提交以前其余外界是不能修改这条数据的,可是这种处理方式效率比较低,通常不推荐使用。注意:在事务中,只有select…for update(排他锁) 或lock in share mode(共享锁) 操做同一个数据时才会等待其它事务结束后才执行,通常select... 则不受此影响。例如在 T3中执行select p.productCount from product p where p.productId = 1;则能正常查询出数据,不会受第一个事务的影响。
百度百科:
乐观锁机制采起了更加宽松的加锁机制。乐观锁是相对悲观锁而言,也是为了不数据库幻读、业务处理时间过长等缘由引发数据处理错误的一种机制,但乐观锁不会刻意使用数据库自己的锁机制,而是依据数据自己来保证数据的正确性。
其余知识点
实现乐观锁通常来讲有如下2种方式:
使用版本号 使用数据版本(Version)记录机制实现,这是乐观锁最经常使用的一种实现方式。何谓数据版本?即为数据增长一个版本标识,通常是经过为数据库表增长一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当咱们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,若是数据库表当前版本号与第一次取出来的version值相等,则予以更新,不然认为是过时数据。
使用时间戳 乐观锁定的第二种实现方式和第一种差很少,一样是在须要乐观锁控制的table中增长一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version相似,也是在更新提交的时候检查当前数据库中数据的时间戳和本身更新前取到的时间戳进行对比,若是一致则OK,不然就是版本冲突。
使用场景举例:以MySQL InnoDB为例
做为演示,咱们继续使用以前的数据库表:product表
productId | productName | productPrice | productCount | version |
---|---|---|---|---|
1 | 小米 | 1999 | 100 | 1 |
2 | 魅族 | 1999 | 100 | 2 |
咱们以版本号
实现的方式进行说明。
操做
查询当前事务隔离级别:
SELECT @@tx_isolation;
结果:
REPEATABLE-READ
复制代码
下面同时打开两个窗口模拟2个用户并发访问数据库
第一种测试
时间轴 | 用户A | 用户B |
---|---|---|
T1 | start transaction; | |
T2 | select * from product p where p.productId = 1;(productCount=100) | |
T3 | update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影响的行: 1) | |
T4 | start transaction; | |
T5 | select * from product p where p.productId = 1;(productCount=100) | |
T6 | update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(等待中...) | |
T7 | commit; | |
T8 | T6执行(受影响的行: 0) | |
T9 | commit; |
流程说明
第二种测试
时间轴 | 用户A | 用户B |
---|---|---|
T1 | select * from product p where p.productId = 1;(productCount=100) | |
T2 | update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影响的行: 1) | |
T3 | select * from product p where p.productId = 1;(productCount=100) | |
T4 | update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影响的行: 0) |
乐观锁小结
乐观锁不须要数据库底层的支持!
悲观锁
比较适合写入操做比较频繁的场景,若是出现大量的读取操做,每次读取的时候都会进行加锁,这样会增长大量的锁的开销,下降了系统的吞吐量。
乐观锁
比较适合读取操做比较频繁的场景,若是出现大量的写入操做,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层须要不断的从新获取数据,这样会增长大量的查询操做,下降了系统的吞吐量。
本章节主要简单介绍了数据库中乐观锁与悲观锁
的相关知识,后续咱们将会继续介绍数据库中的其余锁以及相关知识。例如行锁、表锁、死锁、
欢迎关注公众号:Coder编程 获取最新原创技术文章和相关免费学习资料,随时随地学习技术知识!
参考文章:
chenzhou123520.iteye.com/blog/186095…
chenzhou123520.iteye.com/blog/186340…