mysql 默认是可重复读的隔离级别,这种默认会有幻读,幻读指的什么现象呢,就是在同一个事物中前后两次查到的结果不一致,那么mysql是怎么解决幻读的呢,这就是mvcc
mvcc
什么是mvcc呢,就是多版本并发控制,具体是通过undolog来进行实现的,先来说下事物吧,每个事务开始,都会记录当前readview,包含的就是当前进行中的事物,最大的事务id,主要就是为了处理当前事务对那些数据可见,当数据进行增加修改删除的时候,会生成对应的一条undollog,里面记录了数据的事务id,也就是通过哪个事务进行修改的来的,对应的事务id是不是对当前事务可见,如果不可见的,就顺着undolog链表向上查找,找到对当前可见的数据,这样的readview 和 undolog组成了mvcc,也就是处理了幻读的问题,新增修改删除对当前事务都不可见,这种称之为快照读,那么就还有一个当前读,也就是select for update,或者说直接修改 update table set age=18 where name =‘lisi’
其实修改也是需要当前读的,需要先查找到对应的数据才能进行修改。
mysql 怎么处理当前读的幻读问题呢
当前读就不能走快照了,要查实际的数据,但是还不能幻读,也就是要加锁,最简单的实现就是加上表锁,这样别的请求都不能修改,但是并发粒度比较低,,性能差,mysql是怎么做的呢,采用的是gap lock,也就是间隙锁,间隙锁可以锁定记录之间的返回,比如select * from table where age=18 for update,如果age没有索引,那么就锁整个表,修改删除等操作要注意索引,如果存在索引,存在当前记录的话,会锁记录以及记录周围的间隙,如果不存在记录,就向前找到比18小的,向后找到比18大的,进行加间隙锁,间隙锁之间是不冲突的,也就是多个事务都可以加间隙锁,因为间隙锁就是为了防止对这个范围内的数据进行修改,多次间隙锁不冲突,但是,如果想要再去添加数据,那么就需要等待,如果两个事务都想添加,那么就死锁了
间隙锁影响范围内的增加删除和修改吗?
有时候听到有人会这么问,其实这个对间隙锁表述不正确,间隙锁锁的就是没有数据的范围,所以不存在修改和删除的问题,阻止插入,那么为什么有人这么问呢,因为没把间隙锁,记录锁,next-key lock详细区分,记录锁就是锁的一条记录,间隙锁加上记录锁就是next-key lock 也叫临键锁,间隙锁之间不冲突,但是记录锁之间可是冲突的,常见的修改涉及到一个范围的时候,范围内的记录会加上记录锁,中间范围会有间隙锁,这个时候范围内既不允许增加,也不允许修改和删除
间隙锁导致的死锁场景
什么情况下会间隙锁会导致死锁呢,比如两个事务同时修改一条不存在的记录,这时候会在前后范围加上间隙锁,如果再进行插入数据,那么就都需要插入锁,需要等待对方释放间隙锁,这时候就发生了死锁。
常见的避免死锁的办法
- 对于特定场景修改的时候,可以先查询出来,再根据主键进行修改,或者插入
- 设置事务等待锁的超时时间。当一个事务的等待时间超过该值后,就对这个事务进行回滚,于是锁就释放了,另一个事务就可以继续执行了。在 InnoDB 中,参数 innodb_lock_wait_timeout 是用来设置超时时间的,默认值时 50 秒。当发生超时后,就出现下面这个提示:
- 开启主动死锁检测。主动死锁检测在发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑,默认就开启。当检测到死锁后,就会出现下面这个提示: