锁的类型
共享锁和排他锁
共享锁和排他锁是InnoDB中的行级锁
共享锁是为了当前事务读数据
排他锁是为了当前事务去修改某一行数据(update,insert,delete)
共享锁设置:select row from tablename where id=xxx LOCK IN SHARE MODE
排他锁设置:select row from tablename where id=xxx FOR UPDATE
意向锁
为什么要意向锁?
意向锁是为了能够使行级锁和表级锁能够共存
意向锁是表级锁,用来说明事务稍后会对表中的数据行加哪种类型的锁(共享锁,排他锁)
能够提升效率,因为比如有一个表加了一个行级锁,比如排他锁,如果另外一个事务要读取或者修改表中的某些数据,则不需要一行一行的判断到底是哪一行加了锁,直接判断这个表有没有意向锁,有就阻塞
记录锁
记录锁是一个在索引记录之间的锁
一个间隙可能跨越单个索引值,多个索引值,或者甚至为空
对于使用唯一索引来搜索唯一行的语句,只加记录锁不加间隙锁(不包括组合唯一索引),更多可以看这个:链接
临键锁
插入意向锁
自增锁事务插入到有自增列的表中而获得的一种特殊的表级锁,如果一个事务正在向表中插入数据,那么任何其他事务必须等待,从而保证第一个事务插入的行是连续的自增值
锁的实现
innodb中行级锁是通过给索引加锁来实现的,没有索引的话,引擎会通过隐藏的聚簇索引来对记录进行加锁(表级锁)
为了效率,MySQL进行了优化,对于不满足条件的记录,会进行放锁,最终持有的是满足条件的记录上的锁,但是不满足条件的记录上面依旧会有加锁,放锁的动作,没有索引时,不满足条件的记录行会有加锁,解锁的耗时过程
主键索引:引擎会锁定对应主键的索引
非主键索引:会先锁定非主键索引,在锁定对应的主键索引
锁在事务隔离级别的应用
- 读未提交:select不加任何锁,会产生脏读,并发性最高
- 读提交:普通的select语句时快照读,除了在外键约束检查和重复键检查时会封锁区间,其他情况都只使用记录锁
- 可重复读:用MVCC来实现也行,普通的select语句是快照读,在唯一索引上面使用唯一的查询条件,使用记录锁;在唯一索引上使用范围查询条件,则使用间隙锁和临键锁
- 串行化:所有的select语句都会被隐式加锁,共享锁
快照读,当前读
理解这两个概念能够更好的理解MVCC并发控制
在MVCC中,读操作分成两种,快照读和当前读
快照读,读取的是记录的可见版本或者历史版本,不用加锁
当前读,读取的是记录的最新版本,当前读返回的记录,都会加上锁,从而保证其他事务不会在并发修改这条记录
MVCC只能在提交读和可重复读两个隔离级别下面工作
关于MVCC的具体内容可以看我写的同栏目的内容
快照读
正常的select语句就是快照读
快照读使得在可重复读隔离级别下一个普通的select语句也能做到可重复读
当前读
insert,update,delete以及显式的加锁的select语句是当前读
insert,update,delete执行的时候都会执行一个读取当前数据最新版本的过程
死锁
死锁的情况:有两个事务同时操作两个表,加锁的顺序不一致而产生的,比如事务A锁住a表,事务B锁住b表,当事务A要去锁b表 的时候发现被事务B锁住了,于是等待且不释放a表,当事务B要去锁a表的时候发现a表被事务A锁住了,于是产生了死锁
innodb中有专门发现检测死锁的机制,解决办法就是rollback到较小的事务,也就是数据量较小的
在可重复读隔离级别下,如果两个线程同时对相同条件用select。。。for update加排他锁,在没有符合条件的记录情况下,两个线程都会加锁成功,程序发现记录不存在,就试图插入一条新数据,两个线程都这么做,就会出现死锁,如果将隔离级别改成提交读,就可以避免问题