你能讲讲MySQL 中的锁吗?|MySQL 系列9
作者 | 小汪哥写代码
来源 | 小汪哥(ID:xwgcoding)
前言
上一篇我们分享了MVCC,知道了MVCC的实现原理。那么事务利用MVCC进行的读取操作称之为一致性读,或者一致性无锁读,大部分文章也称之为快照读。所有普通的SELECT语句在RC、RR隔离级别下都算是一致性读。例如
select * from t;
一致性读并不会对表中的任何记录做加锁操作,所以速度非常快。
但是上一篇文章中也提到MVCC 在RR 级别下并不能防止幻读,解决幻读问题靠的是间隙锁,例如:
begin;
#假设users表为空,下面查出来的数据为空
select * from users lock in share mode; #加上共享锁
#此时另一个事务B想提交且插入了一条id=1的数据,由于有间隙锁,所以要等待
select * from users; #读快照,查出来的数据为空
update users set name='mysql' where id=1;#update是当前读,由于不存在数据,不进行更新
select * from users; #读快照,查出来的数据为空
commit;
#事务B提交成功并插入数据
用锁来访问数据的方式我理解为锁定读,那么现在我们来聊一聊锁。
抽象的来理解一下锁
我理解锁就是用来排序的,在特点场景下,来保证事物顺序执行。锁的实现的抽象也是内存中的一个对象,比如JAVA 里的对象头等等。那么mysql 中的锁其实也是内存中的一个结构。
在事务执行前本来是没有锁的,也就是说一开始是没有锁结构,比如下面这条记录:
当一个事务想对这条记录做改动时,首先会看看内存中有没有与这条记录关联的锁结构,当没有的时候就会在内存中生成一个锁结构与之关联。比方说事务80要对这条记录做改动,就需要生成一个锁结构与之关联:
事物120 也要对这条记录进行修改,发现已经有一个锁结构了,那么自己也会生成一个锁结构。只不过is_waiting=true
当80 执行完成后唤醒120
梳理一下锁的内存结构
基本的信息:
- trx信息:代表这个锁结构是哪个事务生成的。
- is_waiting:代表当前事务是否在等待。
其他信息:
- 索引信息:记录一下加锁的记录是属于哪个索引的
- 锁的类型:是行锁还是表锁。
- 锁的模式:共享锁还是排他锁。
- 行锁的具体类型:next-key锁、gap锁、记录锁等
- 表锁/行锁信息:
- 表锁:记载着这是对哪个表加的锁,还有其他的一些信息
- 行锁:
- Space ID:记录所在表空间。
- Page Number:记录所在页号。
- n_bits:页中记录的位图
以上就是对一个锁的宏观认识。
锁的分类
大的分类
- 共享锁,英文名:Shared Locks,简称S锁。读读不互斥,读写互斥。在事务要读取一条记录时,需要先获取该记录的S锁。
- 独占锁,也常称排他锁,英文名:Exclusive Locks,简称X锁。读读、读写都互斥。在事务要改动一条记录时,需要先获取该记录的X锁。
引擎级别的分类
其他存储引擎中的锁
对于MyISAM、MEMORY、MERGE这些存储引擎来说,它们只支持表级锁,而且这些引擎并不支持事务。
SELECT操作,就相当于为这个表加了一个表级别的S锁
UPDATE操作,相当于要获取表的X锁
InnoDB中的表级锁
表级别的S锁、X锁
修改表的互斥其实是通过在server层使用一种称之为元数据锁(英文名:Metadata Locks,简称MDL)东东来实现的,一般情况下也不会使用InnoDB存储引擎自己提供的表级别的S锁和X锁。
表级别的AUTO-INC锁:
事务在持有AUTO-INC锁的过程中,其他事务的插入语句都要被阻塞,可以保证一个语句中分配的递增值是连续的。
在生成当次插入语句需要用到的AUTO_INCREMENT列的值之后,就把该AUTO-INC锁释放掉,并不需要等到整个插入语句执行完才释放锁。
InnoDB中的行级锁
Record Locks:仅仅把一条记录锁上
Gap Locks:把当前记录与当前记录的前一条直接的区间记录锁上。
Next-Key Locks:简称为next-key锁,是Record Locks 和Gap Locks 的结合体。
锁定读语句
对读取的记录加S锁:
SELECT ... LOCK IN SHARE MODE;
对读取的记录加X锁:
SELECT ... FOR UPDATE;
DELETE:
DELETE操作的过程看成是一个获取X锁的锁定读。
UPDATE操作时分为2种:
- 主键值未修改:过程看成是一个获取X锁的锁定读。
- 主键值修改 :则相当于在原记录上做DELETE操作之后再来一次INSERT操作。
INSERT:
一般情况下,新插入一条记录的操作并不加锁。有时候会加插入意向锁【比如:一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了的gap锁,如果有的话,插入操作需要等待,直到拥有gap锁的那个事务提交。】
两阶段加锁协议
平时工作中,为了提升mysql使用 的性能,除了要理解上一篇文章中的MVCC,还有一个就是二阶段锁协议。
事务 A 持有两个记录的行锁,都是在 commit 的时候才释放的,所以事务 B 的 update 就会被阻塞,直到事务 A 执行 commit 之后,事务 B 才能被继续执行。也就是说,在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,需要等事务结束时才释放.
这就是两阶段锁协议(2PL),分为加锁阶段和解锁阶段,所有的 lock 操作都在 unlock 操作之后.
注意:
2PL,两阶段加锁协议:主要用于单机事务中的一致性与隔离性。
2PC,两阶段提交协议:主要用于分布式事务。
MySql innodb引擎里面的采用两阶加锁协议,保证单机事务的隔离和一致性。