MySQL的读有几种类型,其中包括:一致性读(快照读)、加锁读。我们平时使用的select * from user where name = '张三'就属于一致性读,也称为快照读,此时,MySQL根据隔离级别的不同所在不同的时刻生成一次ReadView,以此来防止如不可重复读,幻读等现象。而如果使用的是select * from user where name = '张三' FOR UPDATE;或者select * from user where name = '张三' LOCK IN SHARE MODE;则使用的是加锁读,此时不会使用快照而是直接读取记录的数据,并且使用FOR UPDATE是加了X读,也称为写锁,LOCK IN SHARE MODE表示使用S锁,也称为读锁,其中S锁与S锁兼容,与X锁不兼容,X锁与S锁和X锁都不兼容。并且加锁读是也是可以防止不可重复读和幻读的。另外:当我们使用UPDATE和DELETE语句是其实也是一种加锁读。
行级锁:S锁、X锁
意向锁:意向S锁(IS锁)、意向X锁(IX锁)
表级锁:表级S锁、表级X锁
间隙锁
临键锁
插入意向锁
隐式锁
行级锁分为S锁(读锁),X锁(写锁)顾名思义是对某一行进行加锁,其中S锁与S锁兼容,与X锁不兼容,X锁与S锁和X锁都不兼容。
当某个SQL执行对某个表进行S锁或者X锁的操作时,会先对这个表加意向S锁或者意向X锁,至于原因,下文会讲述。另外,IS锁和IX锁之间都是互相兼容的。
顾名思义,表级S锁和表级X锁都是对表进行加锁的,并且在对某个表进行表级S锁或者表级X锁的加锁前,会先查询该表是否存在IS锁或者IX锁,如果存在则需要等待IS锁或者IX锁释放时才能进行加锁。所以IS锁和IX锁的存在是为了在MySQL需要对某个表加表级锁时,可以不需要遍历所有行来查询该表是否存在行锁就可以判断是否能够对该表进行表级锁的加锁。
间隙锁则是对某个间隙进行的加锁,如此时有3行数据,id分别为:1,3,5,当对id=5的记录加间隙锁时,此时会锁上(3,5)之间的缝隙,在此缝隙上不能新增数据,可以防止幻读,而临键锁则为GAP锁加行锁的结合,临键锁可以防止:幻读和不可重复读。另外:间隙锁是可以重复添加的,如果某个间隙被加了间隙锁,另外一个查询是可以再在这个间隙上添加间隙锁的。何时会使用间隙锁?
有如下数据:
TABLE: hero
| id | name | country |
|---|---|---|
| 1 | l刘备 | 蜀 |
| 3 | z诸葛亮 | 蜀 |
| 8 | c曹操 | 魏 |
| 15 | x荀彧 | 魏 |
| 20 | s孙权 | 吴 |
在RR隔离级别下,执行SQL:select * from hero where number > 1 and number <=15 and country = '魏' FOR UPDATE;
此时会对id=3、8、15、的记录进行加X型的Next Key锁,这么做是为了防止幻读和不可重复读。
当我们需要在某个间隙插入数据时,需要先获取这个间隙的插入意向锁,如果此时该间隙存在GAP锁或者Next Key锁,那么此时的INSERT语句将会被阻塞。
什么是隐式锁?考虑以下场景:
有如下数据:
TABLE: hero
| id | name | country |
|---|---|---|
| 1 | l刘备 | 蜀 |
| 3 | z诸葛亮 | 蜀 |
| 8 | c曹操 | 魏 |
| 15 | x荀彧 | 魏 |
| 20 | s孙权 | 吴 |
线程一:
执行 INSERT hero values(22, 'G关羽', '蜀');
事务未提交。
线程二:
执行 select * from hero where id = 22 FOR UPDATE读取这条记录,那么此时是不可以让这条记录被读取到的,否则会出现脏读。但是INSERT语句是不会对数据进行加锁的,此时为了避免这种情况,MySQL会在线程二执行这条SQL前检测这条数据的trx_id列,如果trx_id不是当前活跃线程,则可以执行获取并加锁,而如果是活跃的事务ID,那么线程二就会帮助线程一所在的事务创建一个对该行记录的锁,等待状态为false,表示加锁已经成功,然后为自己所在事务在生成一个该行记录的锁,但是等待状态为true,表示等待加锁成功,然后阻塞。此时就是生成了一个隐式锁。隐式锁是一种赖加载的思想,避免MySQL中存在过多不必要的锁,节约系统资源和减少死锁的风险。
你知道MySQL产生死锁的场景吗?