MySQL 锁
锁仅用于事务,当然咯,平时执行的单条SQL语句其实质也是事务。
共享锁(S锁,读锁)
当事务对某范围内的数据加上共享锁时,该事务可读数据,其他事务也可加上共享锁来读数据,但是所有事务均不能对数据进行修改。
- 共享锁和排它锁是互斥的,即某段数据加上排它锁就不能加共享锁,加了共享锁就不能加排它锁
- 共享锁与共享锁之间并不互斥,即某范围内的数据可以同时被多个事务加上共享锁
- 不属于共享锁 锁定范围内的数据不受上述影响
- 共享锁仅能写在select语句!
⚠️tips
我在开发时发现,如果仅有一个事务对某段数据加上共享锁,那么在 隔离级别是可重复读的前提下 ,此事务执行CRUD操作是可以成功的。
# 加锁
SELECT * FROM [表] WHERE [条件] LOCK IN SHARE MODE;
# 或(建议使用)
SELECT * FROM [表] WHERE [条件] FOR SHARE;
排它锁(X锁,写锁)
事务A和事务B同时开始执行,且两事务之间均有对某范围内的N条数据的修改操作。假如A事务首先拿到排它锁,那么事务B只能等到事务A释放排它锁以后才能获取到排它锁,进而执行CRUD操作,在未获取到排它锁之前,事务B的读操作可正常执行但CRUD操作将被阻塞
- 排它锁与排它锁之间是互斥的,即某范围内的数据不能被多个事务同时加上排它锁
- 排它锁与共享锁之间是互斥的,即某范围内的数据加上排它锁后就不能再被加上共享锁,或者加上共享锁后就不能被加上排它锁
- 不属于排他锁 锁定范围内的数据不受上述影响
- 排它锁仅能写在select语句
# 加锁
SELECT * FROM [表] WHERE [条件] FOR UPDATE;
意向共享锁(IS锁)
意向共享锁是一种表级锁,只要一张表加上了共享锁,数据库引擎会自动为数据表加上意向共享锁,为什么MySQL要这么设计呢?因为我们的共享锁大多是锁的某范围内的数据,那么由上可知,共享锁与排他锁是互斥的,如果事务A对某范围内数据加了共享锁,事务B又来加排他锁,在没有意向锁的前提下MySQL引擎就会逐条逐条检查数据是否被加上共享锁或排它锁,这显然影响效率,所以当一张表中某范围内的数据被加上共享锁后,MySQL引擎自动给这张表加上 意向共享锁,代表此表有一定范围内的数据被加上了共享锁,那么事务B对此范围内的数据加排他锁时,首先验证表是否有被加意向共享锁,若加了,则不必大费周章去逐条检查数据了。
但若验证表的意向共享锁所锁定的范围内的数据与排它锁所锁定范围内的数据并无交集时,此表也可以加上排它锁。
意向排他锁(IX锁)
意向排他锁是一种表级锁,只要一张表加上了排他锁,数据库引擎会自动为数据表加上意向排他锁,MySQL为什么这么设计呢?同上!
但若验证表的意向排他锁所锁定的范围内的数据与当前事务的排它锁所锁定范围内的数据并无交集时,此表也可以再加上排它锁。
自增锁
自增锁是MySQL中一种特殊的表级锁,专门用于处理自增字段(即具有AUTO_INCREMENT属性的列)的并发插入操作。这种锁的主要目的是确保在并发环境下,每个新插入的记录都能获得一个唯一的、递增的自增值。
但是自增锁管理的自增字段并不会因为回滚而恢复,例:现有一张表,自增id已经到了2,那么下一条数据的id应该3,现在事务A插入了10条数据,然后回滚,接着执行事务B插入一条数据,那么这条数据的自增id的值是13。
临键锁
例:事务中有SQL:select * from g_user where id > 8 and id < 20 for update;
数据表g_user主键为id,表数据如下:
+------+--------+------+
| id | g_name | age |
+------+--------+------+
| 2 | user_2 | 2 |
| 6 | user_6 | 6 |
| 11| user_11| 11|
...略中间数据
| 18 | user_18| 18|
| 22 | user_22| 22|
+------+--------+------+
很显然,根据条件和数据可知表中并无id为8和20的数据,除了为此表加上排它锁以外,还需要一把锁来框定范围,那么MySQL是如何处理的呢?既然表中无id为8和20的数据那么MySQL引擎为此表加上一个临键锁,以此框定范围。
由上述数据可知:表中数据与8差值最少的是6,与20差值最小的是22,既然无id为18和20的数据,那么只能在6,22这俩数据上加锁了。即锁定id为6~22之间的数据。
加锁后,若另一事务插入了一条数据;阻塞情况如下
插入数据的id | 阻塞? | 原因 |
---|---|---|
5 | no | 不在范围(8,20)范围内,介于6之前 |
7 | yes | 不在范围(8,20)范围内,但介于6之后 |
21 | no | 不在范围(8,20)范围内,但介于22之前 |
23 | yes | 不在范围(8,20)范围内,介于22之后 |
由此可知,间隙锁锁最右边距离8最近的叶子节点,锁最左边距离20最近的叶子节点,上述例子中是6与22,就是说6与22之间插入任何数据都会阻塞,即便它不处于(8,20)的范围之间。
间隙锁(GAP)
和临键锁类似,也是锁某范围内的数据,但是按此范围的条件在表中并没有查询到数据,MySQL加的锁,就是临键锁。
例:事务中有SQL:select * from g_user where id > 7 and id < 10 for update;
数据表g_user主键为id,表数据如下:
+------+--------+------+
| id | g_name | age |
+------+--------+------+
| 5 | user_5 | 5 |
| 12| user_12| 12|
+------+--------+------+
很显然,根据条件和数据可知表中并无id处于7和10之间的数据,除了为此表加上排它锁以外,还需要一把锁来框定范围,那么MySQL是如何处理的呢?既然表中即无id处于7与10之间的数据,也无id=7和10的数据,那么MySQL引擎为此表加上一个间隙锁,以此框定范围。
由上述数据可知:表中数据与7差值最少的分别是5,与10差值最小的是12,既然无id为7和10的数据,那么只能在5和12这俩数据上加锁了。即锁定id为5,12这两条数据。
加锁后,若另一事务插入了一条数据;阻塞情况如下
插入数据的id | 阻塞? | 原因 |
---|---|---|
4 | no | 不在范围(7,10)范围内,且介于5之前 |
6 | yes | 不在范围(7,10)范围内,但介于6之后 |
11 | no | 不在范围(7,10)范围内,但介于12之前 |
13 | yes | 不在范围(7,10)范围内,且介于12之后 |
由此可知,间隙锁锁最右边距离id=5最近的叶子节点,锁最左边距离id=10最近的叶子节点,上述例子中是5与12,就是说5与12之间插入任何数据都会阻塞,即便它不处于(7,10)的范围之间。
记录锁(REC_NOT_GAP)
按具体条件查询的某一条或几条数据,而不是按范围查询,MySQL对筛选出来的记录加上记录锁。
# 例
select * from g_user where id = 3 for update ;
select * from g_user where id = 3 or id =4 or id =5 for update ;
select * from g_user where id in (3,4,5) for update ;
select * from tp where id between 1 and 5 for update ;
查看锁的情况
SELECT * FROM performance_schema.data_locks;
- ENGINE:MySQL引擎,如innodb
- ENGINE_LOCK_ID:
- ENGINE_TRANSACTION_ID:事务ID,同一事务下的锁的此字段值一样
- THREAD_ID:线程ID,这个不重要,同一事务的线程ID值一样
- EVENT_ID:事件ID
- OBJECT_SCHEMA:哪个库
- OBJECT_NAME:哪张表
- PARTITION_NAME:
- SUBPARTITION_NAME:
- INDEX_NAME:索引类型,如primary
- OBJECT_INSTANCE_BEGIN:
- LOCK_TYPE:锁类型(表锁:TABLE,行锁:RECORD)
- LOCK_MODE:锁模式(IX,IS,X,S,GAP,REC_NOT_GAP)
- LOCK_STATUS:
- LOCK_DATA:锁定的数据,INDEX_NAME中类型的值