MySQL锁详细讲解( 二 )


Read committed: 出现的现象--->不可重复读:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改,例如:A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样【危害:A每次查询的结果都是受B的影响的,那么A查询出来的信息就没有意思了】
Repeatable read: 避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即使被修改了,也只会读取当前事务版本的数据

MySQL锁详细讲解

文章插图
 

MySQL锁详细讲解

文章插图
 
至于虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致 。和不可重复读类似,但虚读(幻读)会读到其他事务的插入的数据,导致前后读取不 一致,幻读的重点在于新增或者删除(数据条数变化),不可重复读的重点是修改,幻读和不可重复的区别?
乐观锁和悲观锁无论是Read committed还是Repeatable read隔离级别,都是为了解决读写冲突的问题,现在考虑一个问题:有一张数据库表USER,只有id、name字段,现在有2个请求同时操作表A,过程如下:(模拟更新丢失,虽然不是很恰当)
 1. 操作1查询出name="zhangsan"
 2. 操作2也查询出name="zhangsan"
 3. 操作1把name字段数据修改成lisi并提交
 4. 操作2把name字段数据修改为wangwu并提交
那么操作1的更新丢失啦,即一个事务的更新覆盖了其它事务的更新结果,解决上述更新丢失的方式有如下3种:
  • 使用Serializable隔离级别,事务是串行执行的!
  • 乐观锁
  • 悲观锁
悲观锁
我们使用悲观锁的话其实很简单(手动加行锁就行了):select * from xxxx for update,在select 语句后边加了for update相当于加了排它锁(写锁),加了写锁以后,其他事务就不能对它修改了!需要等待当前事务修改完之后才可以修改.也就是说,如果操作1使用select ... for update,操作2就无法对该条记录修改了,即可避免更新丢失 。
乐观锁
乐观锁不是数据库层面上的锁,需要用户手动去加的锁 。一般我们在数据库表中添加一个版本字段version来实现,例如操作1和操作2在更新User表的时,执行语句如下:
update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},此时即可避免更新丢失 。
间隙锁GAP当我们用范围条件检索数据而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在 的记录,叫做“间隙(GAP)” 。InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁 。例子:假如emp表中只有101条记录,其empid的值分别是1,2,...,100,101
Select * from emp where empid > 100 for update;上面是一个范围查询,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁
InnoDB使用间隙锁的目的有2个:
  • 为了防止幻读(上面也说了,Repeatable read隔离级别下再通过GAP锁即可避免了幻读)
  • 满足恢复和复制的需要:MySQL的恢复机制要求在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读
死锁并发的问题就少不了死锁,在MySQL中同样会存在死锁的问题
锁总结表锁其实我们程序员是很少关心它的:
  • 在MyISAM存储引擎中,当执行SQL语句的时候是自动加的 。
  • 在InnoDB存储引擎中,如果没有使用索引,表锁也是自动加的 。
现在我们大多数使用MySQL都是使用InnoDB,InnoDB支持行锁:
  • 共享锁--读锁--S锁
  • 排它锁--写锁--X锁
在默认的情况下,select是不加任何行锁的~事务可以通过以下语句显示给记录集加共享锁或排他锁 。
  • 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
  • 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE
InnoDB基于行锁还实现了MVCC多版本并发控制,MVCC在隔离级别下的Read committed和Repeatable read下工作 。MVCC实现了读写不阻塞

【MySQL锁详细讲解】


推荐阅读