大家好,熬了几个中午,终于把这篇文章给整理出来了,锁是大家在工作和面试中老大难的问题,因此,特意整理了之前看的一些资料,跟着大家一起总结一下MySQL的锁;至于为什么需要锁?感觉这个应该就不需要我多说了,大家想想这个场景就大致可以get到了:
你在商场的卫生间上厕所,此时你一定会做的操作是啥?锁门。如果不锁门,
当你正在上着的时候,啪一下门就打开了,可能大概也许似乎貌似有那么一丁点的不太合适。
同样,对于数据也是一样,在并发的场景下,如果不对数据加锁,会直接破坏数据的一致性。
当然,写这篇文章,也是为了回答这篇文章(灵魂一问?MySQL是如何解决幻读问题的)最后的一个问题做个铺垫,在此处,先对mysql锁做个总结,然后后面会有文章结合InnoDB存储引擎数据页结构来解释next-key锁是怎么解决幻读问题的。
1. 共享锁与独占锁
从锁是否互相兼容的角度来看,锁可以分为共享锁和独占锁,不管是行锁,还是表锁,都存在这两种类型的锁
1.1 行锁级别下的共享锁与独占锁
-
S锁:共享锁(Shared Locks),在事务要读取一条记录时,需要先获取该记录的S锁。 -
X锁:排他锁(Exclusive Locks),在事务要改动一条记录时,需要先获取该记录的X锁。
1.1.1 兼容性
| 兼容性 | X锁 | S锁 |
|---|---|---|
X锁 | 不兼容 | 不兼容 |
S锁 | 不兼容 | 兼容 |
- 假如事务T1首先获取了一条记录的S锁之后,事务T2接着也要访问这条记录,
- 如果T2想要在这条记录获取S锁,那么T2不会被阻塞,意味着T1和T2共同持有这条记录的S锁
- 如果T2想要在这条记录获取X锁,则T2 会被阻塞,直到T1释放S锁,
1.2 表锁级别下的共享锁与独占锁
对一个表加锁影响整个表中的记录,我们就说这个锁的粒度比较粗。给表加的锁也可以分为共享锁(S锁)和独占锁(X锁);其兼容性,同行锁的兼容性一样。
但是不知道小伙伴有没有思考过,加表锁之前,是否需要考虑表中的记录是否存在行锁,答案当然是要考虑的,那么如果加表锁之前,是怎么判断是否存在行锁的呢?是依次查找表中每一条记录是否存在行锁吗?当然不是,为此MySQL设计者们,专门提出了意向锁的东西,很好解决了加表锁之前,高效判断是否存在行锁冲突。
1.2.1 意向锁
意向锁英文名为Intention Locks,其又可以分为IS锁和IX锁,下面我们再来看看它们的概念和互斥性:
IS锁:意向共享锁(Intention Shared Lock),当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁
·IX锁·:意向独占锁(Intention Exclusive Lock),事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。
注:IS锁和IX锁是在对某条记录加行锁的时候,加的表锁.
1.2.1.1 兼容性
| 兼容性 | X锁 | IX锁 | S锁 | IS锁 |
|---|---|---|---|---|
X锁 | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
IX锁 | 不兼容 | 兼容 | 不兼容 | 兼容 |
S锁 | 不兼容 | 不兼容 | 兼容 | 兼容 |
IS锁 | 不兼容 | 兼容 | 兼容 | 兼容 |
在了解这个兼容性的时候,要注意,IS锁和IX锁在什么时候锁上去的,是对某条记录加行锁的时候,给表加的锁。举个例子,IX和IX锁是互相兼容的,因为IX锁是在对某条行记录有修改操作的时候产生的,它的加锁,只是标志存在行锁的X锁,所以两把IX并不会互相冲突。
2. 表锁
2.1 加锁语法
InnoDB存储引擎提供的表级S锁或者X锁只会在一些特殊情况下,比方说崩溃恢复过程中用到,当然我们还是可以手动获取一下的,比方说在系统变量autocommit=0,innodb_table_locks = 1时,手动获取InnoDB存储引擎提供的表t的S锁或者X锁可以这么写:
LOCK TABLES t READ:InnoDB存储引擎会为当前会话对表t加表级别的S锁,会阻塞自己和其它会话的update操作,其它会话可以对此表使用LOCK TABLES t READ,但是不能对此表加LOCK TABLES t WRITELOCK TABLES t WRITE:InnoDB存储引擎会为当前会话对表t加表级别的X锁,会阻塞其他会话对此表的读写操作unlock tables:释放锁当前会话的锁
2.2 AUTO-INC锁
在MySQL中,AUTO-INC锁专门针对插入 AUTO_INCREMENT 类型的列,在建表的时候,我们经常会设置id列自增,如下面的DDL语句,设置 person表的id为自增:
CREATE TABLE person
(
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100),
age int,
PRIMARY KEY (id)
) Engine = InnoDB;
由于这个表的id字段声明了AUTO_INCREMENT,当我们往表insert的时候,系统会在执行这条语句的时候,自动对表加AUTO-INC锁,在该insert语句执行结束的时候,再把AUTO-INC锁自动释放掉。这样,当其它事务在执行插入的时候,都会被阻塞。
2.3 AUTO-INC轻量级锁
由于AUTO_INCREMENT 锁是表锁,力度较粗,因此,MySQL设计者们在此基础上又提出了AUTO-INC轻量级锁:AUTO_INCREMENT。
如果我们的插入语句在执行前就可以确定具体要插入多少条记录,比方说我们上边举的关于表person的例子中,在语句执行前就可以确定要插入多少条记录,那么一般采用轻量级锁的方式对AUTO_INCREMENT修饰的列进行赋值。这种方式可以避免锁定表,可以提升插入性能。
3. 行锁
3.1 加锁语法
加S锁:
-- 显示加S锁
select ... LOCK IN SHARE MODE;
加X锁:
select... for update
注:
-
默认情况下,在读取记录时就获取记录的S锁,
3.2 LOCK_REC_NOT_GAP
LOCK_REC_NOT_GAP锁是官方叫法,在这里,我们暂且可以叫做记录锁,该锁锁的是一条记录锁;同时需要注意的是,记录锁的锁定对象是对应那行数据所对应的索引。如下图所示:我们把id=3的记录加上记录锁,该锁锁的就是该条记录:
记录锁也有S锁和X锁之分的,当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可以继续获取X型记录锁;当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不可以继续获取X型记录锁。
当我们执行SELECT * FROM person WHERE id = 3 FOR UPDATE语句时,就会对值为3的索引加上X型记录锁,执行SELECT * FROM person WHERE id = 3 LOCK IN SHARE MODE语句时,就会对值为3的索引加上S型记录锁。
记录锁,通过官方字样:LOCK_REC_NOT_GAP;NOT_GAP说明记录锁是一种非间隙锁,那我们再继续看看GAP锁
3.3 Gap Locks
Gap Locks 又称之为间隙锁,其与LOCK_REC_NOT_GAP锁一样,锁定的对象也是索引,但不同的是,Gap Locks 锁的不仅仅是一行记录,而是一个范围;如下所示,我们给id=8的行记录加一个gap锁:
如上所示,给id为8的行记录加上锁,意味着不允许别的事物在id范围为
(3,8)插入新的记录,即此把Gap Locks锁 锁的范围为(3,8),当有另外一个事物再想插入id为7的记录的时候,它就会被阻塞,直到当前事物将这把Gap Locks锁释放掉。
值得注意的是,Gap Locks锁虽然也分为共享Gap Locks锁和独占Gap Locks锁,但是如果你对一条记录加了Gap Locks锁(不管是共享Gap Locks锁还是独占Gap Locks锁),并不会限制其他事务对这条记录加记录锁或者继续加Gap Locks锁,因为Gap Locks锁只是为了防止在某个范围插入新的数据,形成幻影记录。
3.4 Next-Key Locks
Next-Key锁其实是记录锁和间隙锁的组合,当我们既想锁住某条记录,又想阻止其他事务在该记录前边的间隙插入新记录,这个时候,Next-Key Locks锁就该上场了;如下,我们给id为8的那条记录加上next-key锁的示意图如下:
正如图中所展示的一样,next-key锁能够锁住的范围为(3,8],它既能保护当条记录,又能阻止其它事物将新的记录插入被保护记录的前边间隙中。
3.5 Insert Intention Locks
为了便于记忆,后续将Insert Intention Locks 简称为插入意向锁,通过前面的Gap锁的总结,我们知道,当一个事物在插入一条记录的时候,需要判断插入的位置是否被别的事物加了Gap锁,如果有的话,插入操作就需要等待,直到拥有Gap锁的事物提交,那么当有两个不同的事物T1,T2分别需要插入id为5和id为7的记录呢?为了提高性能,MySQL设计者们提出了插入意向锁,插入意向锁之间不会发生互斥,但是会和其他的Gap锁发生互斥,这样既保证了Gap锁锁定的范围不会产生幻影记录,又保证了事物T1和T2不会互相阻塞。
总结
意向锁是表级锁,它的提出是为了在加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录。InnoDB存储引擎既支持表锁,也支持行锁。表锁实现简单,占用资源较少,不过粒度很粗,有时候你仅仅需要锁住几条记录,但使用表锁的话相当于为表中的所有记录都加锁,所以性能比较差。行锁粒度更细