MYSQL锁的分类
1.全局锁
整个数据库处于只读状态 ,增删改会被阻塞
通常用于数据备份
如何避免全局锁? 在可重复读级别下会开启一个事务,整个事务执行期间会使用这个readView
flush tables with read lock
unlock tables
2.表级锁
2.1表锁
READ(共享锁,读锁)
WRITE(排他锁,写锁)
//表级别的共享锁,也就是读锁;
//允许当前会话读取被锁定的表,但阻止其他会话对这些表进行写操作。
lock tables t_student read;//表级别的独占锁,也就是写锁;
//允许当前会话对表进行读写操作,但阻止其他会话对这些表进行任何操作(读或写)。
lock tables t_stuent write;//解锁
UNLOCK TABLES;
使用了read,本线程只能读,写也会被阻塞,同时本线程不能访问其他表
注意:如果一个线程已经对某张表加了 WRITE(写锁,排他锁),则其他线程不能再获取该表的任何锁(包括 READ
读锁和 WRITE
写锁)。
2.2元数据锁
- 对一张表进行 CRUD 操作时,加的是 MDL 读锁;
- 对一张表做结构变更操作的时候,加的是 MDL 写锁;
元数据锁的目的是,反正我们在对这张表进行crud操作的时候,表结构发生了变更
他在我们执行事务的时候自动开启,事务执行完毕后释放
注意:假如我们有一个长事务,对该表进行一个crud操作,此时,我修改这张表结构,由于前者已经拿到了MDL读锁,因此在申请MDL写锁的时候会被阻塞. 但是申请写锁被阻塞后,同样会阻塞后续的crud操作 这是因为申请 MDL 锁的操作会形成一个队列,队列中写锁获取优先级高于读锁,一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作。
2.3意向锁
意向锁的目的是为了快速判断表里是否有记录被加锁
当我们执行增删改操作的时候,会对表中的记录加上独占锁
普通的select语句不会对记录加锁
而select for update 会对记录加独占锁,select lock in share mod会对记录加共享锁
在他们对表中的记录加共享锁/独占锁之前 会给表现加上一个意向共享锁/意向独占锁
如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。
2.4 AUTO-INC 锁
我们在插入数据的时候,如果设置了注解自增,那么在插入时可以不指定主键的值,数据库会自动给主键赋值递增的值,这主要是通过 AUTO-INC 锁实现的。
AUTO-INC 锁是一个特殊的锁,他不是在事务执行完才释放,而是插入语句执行完之后就释放
但是, AUTO-INC 锁再对大量数据进行插入的时候,会影响插入性能,因为另一个事务中的插入会被阻塞。
因此mysql后续版本进行了优化,提供了一种轻量级的锁来实现自增,不再是插入完才释放锁,而是给这个字段赋完了之后就释放了
InnoDB 存储引擎提供了个 innodb_autoinc_lock_mode 的系统变量,是用来控制选择用 AUTO-INC 锁,还是轻量级的锁。
但是有当使用innodb_autoinc_lock_mode = 2 也就是轻量级锁 + binlog_format=STATEMENT 可能会导致主从不一致的情况
假设有表 t
sql复制编辑CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY,a INT,b INT
);
步骤
-
Session A 向
t
插入 4 行数据:sql复制编辑 INSERT INTO t (a, b) VALUES (1,1), (2,2), (3,3), (4,4);
- 生成的 ID 依次是
1, 2, 3, 4
。
- 生成的 ID 依次是
-
Session A 复制
t
表,创建t2
:sql复制编辑 CREATE TABLE t2 LIKE t;
-
两个会话(Session A 和 Session B)同时往
t2
插入数据:sql复制编辑-- Session A INSERT INTO t2 SELECT * FROM t WHERE a > 2;-- Session B INSERT INTO t2 SELECT * FROM t WHERE a <= 2;
-
如果
innodb_autoinc_lock_mode = 2
Session B
先执行,分配 ID1, 2
。Session A
申请 ID,得到3
,插入(3,5,5)
。Session B
继续插入,得到 ID4, 5
。
最终
t2
在主库的 ID 是:scss复制编辑(1,1,1) (2,2,2) (3,5,5) (4,3,3) (5,4,4)
-
主库的
binlog_format = STATEMENT
,所以 binlog 记录的 SQL 是:sql复制编辑INSERT INTO t2 SELECT * FROM t WHERE a <= 2; INSERT INTO t2 SELECT * FROM t WHERE a > 2;
但是!
- 在从库上,这两个
INSERT
是按顺序执行的。 - 从库不会并发执行这两个语句,所以 ID 是 连续 的!
- 主库
t2
的 ID 是 1,2,3,4,5,但从库t2
的 ID 可能是 1,2,3,4,5,6,7,8,9,10,导致数据不一致!
- 在从库上,这两个
要解决这问题,binlog 日志格式要设置为 row,这样在 binlog 里面记录的是主库分配的自增值,到备库执行的时候,主库的自增值是什么,从库的自增值就是什么。
3.行级锁
3.1Record Lock 记录锁
锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的
- 当一个事务对一条记录加了 S 型记录锁后,其他事务也可以继续对该记录加 S 型记录锁(S 型与 S 锁兼容),但是不可以对该记录加 X 型记录锁(S 型与 X 锁不兼容);
- 当一个事务对一条记录加了 X 型记录锁后,其他事务既不可以对该记录加 S 型记录锁(S 型与 X 锁不兼容),也不可以对该记录加 X 型记录锁(X 型与 X 锁不兼容)。
有点像读写锁
当执行
begin;
select * from t_test where id = 1 for update;
加的就是X型锁,其他事务就无法修改这条数据
lock in share mod 加的就是S锁
3.2Gap Lock 称为间隙锁
只存在于可重复读的隔离级别下,为解决幻读
假设,表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生
3.3Next-Key Lock 临键锁
相当于是Record Lock
+Gap Lock
锁定一个范围,并且锁定记录本身
3.4插入意向锁
一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)
如果有的话,插入操作就会发生阻塞,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻),在此期间会生成一个插入意向锁,表明有事务想在某个区间插入新记录,但是现在处于等待状态。
举个例子,假设事务 A 已经对表加了一个范围 id 为(3,5)间隙锁。
当事务 A 还没提交的时候,事务 B 向该表插入一条 id = 4 的新记录,这时会判断到插入的位置已经被事务 A 加了间隙锁,于是事物 B 会生成一个插入意向锁,然后将锁的状态设置为等待状态(PS:MySQL 加锁时,是先生成锁结构,然后设置锁的状态,如果锁状态是等待状态,并不是意味着事务成功获取到了锁,只有当锁状态为正常状态时,才代表事务成功获取到了锁),此时事务 B 就会发生阻塞,直到事务 A 提交了事务。
插入意向锁名字虽然有意向锁,但是它并不是意向锁,它是一种特殊的间隙锁,属于行级别锁。
如果说间隙锁锁住的是一个区间,那么「插入意向锁」锁住的就是一个点。因而从这个角度来说,插入意向锁确实是一种特殊的间隙锁。
插入意向锁与间隙锁的另一个非常重要的差别是:尽管「插入意向锁」也属于间隙锁,但两个事务却不能在同一时间内,一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁如果不在间隙锁区间内则是可以的)。