数据库两大神器索引和锁( 五 )


数据库两大神器索引和锁

文章插图
此时,用户李四的操作就丢失掉了:
  • 丢失更新:一个事务的更新覆盖了其它事务的更新结果 。
(ps:暂时没有想到比较好的例子来说明更新丢失的问题,虽然上面的例子也是更新丢失,但一定程度上是可接受的..不知道有没有人能想到不可接受的更新丢失例子呢...)
解决的方法:
  1. 使用Serializable隔离级别 , 事务是串行执行的!乐观锁悲观锁乐观锁是一种思想 , 具体实现是,表中有一个版本字段,第一次读的时候 , 获取到这个字段 。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样 。如果一样更新,反之拒绝 。之所以叫乐观 , 因为这个模式没有从数据库加锁,等到更新的时候再判断是否可以更新 。悲观锁是数据库层面加锁,都会阻塞去等待锁 。
2.3.1悲观锁
所以,按照上面的例子 。我们使用悲观锁的话其实很简单(手动加行锁就行了):
  • select * from xxxx for update
在select 语句后边加了 for update相当于加了排它锁(写锁),加了写锁以后,其他的事务就不能对它修改了!需要等待当前事务修改完之后才可以修改.
  • 也就是说 , 如果张三使用select ... for update,李四就无法对该条记录修改了~
2.3.2乐观锁
乐观锁不是数据库层面上的锁,是需要自己手动去加的锁 。一般我们添加一个版本字段来实现:
具体过程是这样的:
张三select * from table --->会查询出记录出来 , 同时会有一个version字段
数据库两大神器索引和锁

文章插图
李四select * from table --->会查询出记录出来,同时会有一个version字段
数据库两大神器索引和锁

文章插图
李四对这条记录做修改:update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},判断之前查询到的version与现在的数据的version进行比较,同时会更新version字段
此时数据库记录如下:
数据库两大神器索引和锁

文章插图
张三也对这条记录修改:update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},但失败了!因为当前数据库中的版本跟查询出来的版本不一致!
数据库两大神器索引和锁

文章插图
参考资料:
  • ***/p/31537871---什么是悲观锁和乐观锁***/question/27876575---乐观锁和 MVCC 的区别?
2.4间隙锁GAP
当我们用范围条件检索数据而不是相等条件检索数据,并请求共享或排他锁时 , InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)” 。InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁 。
值得注意的是:间隙锁只会在Repeatable read隔离级别下使用~
例子:假如emp表中只有101条记录,其empid的值分别是1,2,...,100,101
Select * from emp where empid > 100 for update;上面是一个范围查询,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁 。
InnoDB使用间隙锁的目的有两个:
  • 为了防止幻读(上面也说了 , Repeatable read隔离级别下再通过GAP锁即可避免了幻读)满足恢复和复制的需要MySQL的恢复机制要求:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录 , 也就是不允许出现幻读
2.5死锁
并发的问题就少不了死锁,在MySQL中同样会存在死锁的问题 。
但一般来说MySQL通过回滚帮我们解决了不少死锁的问题了,但死锁是无法完全避免的,可以通过以下的经验参考,来尽可能少遇到死锁: