- select * from xxxx for update
- 也就是说,如果张三使用select ... for update,李四就无法对该条记录修改了~
乐观锁不是数据库层面上的锁,是需要自己手动去加的锁 。一般我们添加一个版本字段来实现:
具体过程是这样的:
张三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},但失败了!因为当前数据库中的版本跟查询出来的版本不一致!
文章插图
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的恢复机制要求:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读
并发的问题就少不了死锁,在MySQL中同样会存在死锁的问题 。
但一般来说MySQL通过回滚帮我们解决了不少死锁的问题了,但死锁是无法完全避免的,可以通过以下的经验参考,来尽可能少遇到死锁:
- 1)以固定的顺序访问表和行 。比如对两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;将两个事务的sql顺序调整为一致,也能避免死锁 。
- 2)大事务拆小 。大事务更倾向于死锁,如果业务允许,将大事务拆小 。
- 3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率 。
- 4)降低隔离级别 。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁 。
- 5)为表添加合理的索引 。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大 。
上面说了一大堆关于MySQL数据库锁的东西,现在来简单总结一下 。
表锁其实我们程序员是很少关心它的:
- 在MyISAM存储引擎中,当执行SQL语句的时候是自动加的 。
- 在InnoDB存储引擎中,如果没有使用索引,表锁也是自动加的 。
- 共享锁--读锁--S锁
- 排它锁--写锁--X锁
- 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 。
- 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE 。
InnoDB实现的Repeatable read隔离级别配合GAP间隙锁已经避免了幻读!
推荐阅读
- 数据库缓存更新的套路
- html5怎么连接数据库?
- 数据库并发2万就跪了?你需要这份指导性的知识框架
- 优化必备 redis异步访问模式--管道技术
- 北京大医院看病有窍门,真的!挂号一定要认准“两大四小”
- 从拍照到视频,这9款摄影后期APP“装机必备”
- 超全的出境必备APP清单,就算不会英语毫无方向感,也能走遍全球
- Python 连接数据库的多种方法
- 4个MySQL优化工具AWR,帮你准确定位数据库瓶颈!
- mysql怎么导出数据库?