mysql 锁
select ... lock in share mode //共享锁 select ... for update //排他锁
行锁的3种算法:
record lock:单个行记录上的锁
gap lock:锁定范围,不锁定记录本身,只在RR里使用
next-key lock:锁定范围,并锁定记录本身,假如有两个数据3,7,则对区间(-无穷,3],(3,7],(7,正无穷)进行上锁
Previous-Key Lock:和next-key相反,区间左闭右开
innodb对于行的查询使用next-key lock,当查询的索引含有唯一属性时,将next-key lock降级为record key
乐观锁,适合读多写少的情景
MVCC:update table set name = ‘NewValue‘, version = version + 1 where id = #{id} and version = #{version};
CAS:ABA问题,自旋开销大
悲观锁适合频繁写入的情景
快照读:简单的select操作,属于快照读,不加锁。 select * from table where ?;
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。
死锁:指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象
解决死锁:
超时,回滚某一事务,另一事务就能得到执行,缺点是回滚的代价可能很高
wait-for graph(等待图),深度优先算法判断是否存在回路
- 1)以固定的顺序访问表和行。比如对两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;将两个事务的sql顺序调整为一致,也能避免死锁。
- 2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
- 3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
- 4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
- 5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
隔离级别与锁的关系 待验证
在Read Uncommitted级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突
在Read Committed级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁;
在Repeatable Read级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。
SERIALIZABLE 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。
可通过 show engine innodb status 以及 information_schema 库下的innodb_trx、innodb_locks、innodb_lock_waits 观察 lock 的信息。
只有通过索引进行检索时,才会使用行锁,否则使用表级锁