提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
mysql锁机制以及隔离级别下保证并发安全的方式
多事务并发执行可能出现的问题
脏写:多个事务同时对一行数据进行修改,导致一个事务修改的数据被另一方覆盖。如下图,如果事务B想要扣减三百余额,而事务A在事务B执行之后将余额设置为1000,就导致事务B的修改被事务A覆盖
脏读:事务B读取了事务A修改后但未提交的事务,而事务A由于业务进行数据回滚,造成了数据不一致的问题。如下图
不可重复读:事务B多次读取数据,而在多次读取数据之间事务A修改了数据并进行了提交,导致事务B多次读取数据不同,造成了数据不一致问题。如下图。
幻读:事务B进行多次查询,而在多次查询期间由于事务A插入新的数据,导致事务B多次查询数据不同,造成数据不一致问题。如下图。
对于以上由于并发事务导致的线程安全问题,mysql使用了锁机制和MVCC解决这些问题,关于MVCC如何实现以及作用我在这篇文章中有介绍。我们先来讲讲mysql的锁机制
mysql有那些锁
按照锁的粒度来分,mysql的锁可以分为全局锁、表锁和行锁。
按照锁的功能来分,可以分为共享锁和排他锁。
全局锁
对整个数据库加锁,mysql提高了加全局读锁的命令:Flush tables with read lock (FTWRL),加全局读锁之后,mysql只允许读数据的操作,数据的更新操作以及数据库表的结构定义以及修改操作会被阻塞。
表级锁
表级锁包括表锁、元数据锁和意向锁。
表锁:表锁包括表写锁和表读锁。
表读锁:当事务获取表读锁后,会阻塞其他事务获取表写锁,但是其他事务可以再次获取表读锁。
表写锁:当事务获取表写锁后,会阻塞其他事务获取表写锁和表读锁。
也就是表读锁可以被多个事务所持有,而表写锁只能被一个事务所持有,并且表写锁和表读锁会相互阻塞。
元数据锁:元数据锁包括元数据写锁和元数据读锁。主要针对对数据库表结构进行查询修改时进行加锁。
元数据读锁:当查询数据库表结构信息时,对表加锁,会阻塞元数据写锁
元数据写锁:当对数据库表的结构进行修改时,对表加锁,会阻塞元数据读锁
意向锁:当我们想要对整个表的数据进行修改时,需要获取表写锁,而表写锁和行锁肯定时阻塞的,因此需要检查是否含有行锁,如果需要遍历每一行数据,毫无疑问很浪费时间,因此引入了意向锁。
当数据获取行锁后,会添加意向锁,如果对整个表进行修改而获取表写锁,只需要判断有无意向锁即可。
行锁
行锁包括记录锁、间隙锁和临键锁。它们都是排他锁,会相互阻塞。
记录锁:对单个数据行进行加锁。当修一行数据时需要获取记录锁。
间隙锁:当修改一个范围内的数据或者对它们进行当前读时,会加间隙锁。
临键锁:是记录锁和间隙锁的结合,当执行写操作时都会获取临建锁,而临建锁则会根据实际情况退化为记录锁和间隙锁。
在不同的隔离级别下mysql保证并发安全的方式
RU隔离级别
在Read Uncommitted隔离级别下,解决了脏写的问题,但是会发生脏读、不可重复读、幻读的问题
解决方案:通过写操作加排他锁,使得并发写操作相互阻塞来防止脏写。
RC隔离级别
在Read Committed隔离级别下,解决了脏写、脏读的问题,但是会发生不可重复读、幻读的问题
解决方案:通过MVCC机制解决了脏读的问题,对写操作加排他锁保证了脏写的问问题,但是由于每一次快照读都会生成一个Read View会导致不可重复读和幻读的问题。
RR隔离级别
在Repeatable Read隔离级别下,解决了脏写、脏读、不可重复读的问题,但是会发生幻读的问题
解决方案:通过MVCC机制以及一个事务最多会生成一个快照解决了脏读、不可重复读的问题,对写操作加排他锁解决了脏写的问题,但是在特定情况下仍然会导致幻读的问题。
Serializable隔离级别
在Serializable隔离级别下,解决了脏写、脏读、不可重复读、幻读的问题,但是其性能极低
解决方案:所有写操作加排他锁,读操作加共享锁,解决了脏写、脏读、不可重复读、幻读的问题,但是mysql的读写操作会相互阻塞,性能很低,mysql默认使用RR隔离级别。
面试:什么情况下会产生死锁?
如上图所示,线程A对id=10的数据行加了记录锁,而线程B对id=5的数据行加了记录锁,线程A需要获取id=5的行记录锁,而线程B需要获取id=10的行记录锁,它们之间相互阻塞,而锁只有在事务提交或者回滚之后才会释放,它们之间相互等到,互相占用了对方的所需的资源,形成了死锁。