Mysql锁机制
MySQL锁机制
1. 锁定义
锁是计算机协调多个进程或线程并发访问某一资源的机制
MySQL最显著的特点是不同的存储引擎支持不同的锁机制。例如:MyIsAM和MEMORY存储引擎采用的是表级锁,InnoDB存储引擎既支持表级锁也支持行级锁,默认采用行级锁
MySQL锁的特性可归纳为3种锁
表级锁:开销下,加锁快;不会出现死锁;锁定粒度大,发生锁冲突概率最高,并发度最低
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突概率最小,并发度最高
页面锁:开销和加锁介于表级锁和行级锁之间;会出现死锁,锁定粒度介于行级锁和表级锁之间,并发度一般
从上面三种锁的描述可以看出锁的粒度越大,发生锁冲突的概率也就越大,加锁的速度越快,并行度越低,具体使用哪种锁要根据场景确定
2.MySQL存储引擎
可以通过show engines查看MySQL支持的存储引擎
数据库一般支持默认的存储引擎是InnoDB,可以通过show variables like ‘%storage_engine%’;查看
每一种存储引擎的锁机制是不同的
下面主要对使用最多InnoDB进行分析
3.InnoDB锁
InnoDB存储引擎有两个特点:一是支持事务;二是采用行级锁
3.1 事务介绍
一组操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;
事务是一组不可再分割的操作集合(也称为工作逻辑单元)
3.2 事务的四特性(ACID)
原子性(Atomicity):最小工作单元,整个工作单元要么一起提交成功,要么全部失败回滚
一致性(Consistency):事务中操作的数据以及状态改变是一致的,即写入结果必须符合预设的规则,不会因为出现系统意外等原因导致状态不一致
隔离性(Isolation):一个事务所操作的数据在提交之前,对其他事务的可见性设定
持久性(Durability):事务所做的修改永久保存,不会因为系统意外导致数据丢失
3.3 并发事务带来的问题
并发事务带来三个问题:脏读,不可重复读,幻读
脏读:A事务在对一条记录做修改,但是未提交,这条记录处于不一致状态;这时,B事务也来同样读同一条数据,如果不加控制,B读了未修改前的数据,并根据该数据进行进一步处理,就会产生未提交的数据依赖关系,如果现在数据未提交回滚,将导致数据不一致,这种现象叫做脏读
不可重复读:B事务在读取某些数据后的某个时间,再次读取以前读取过的数据,却发现其读取的数据已经发生了改变,这种现象叫做不可重复读
幻读: A事务按照相同查询条件,重新读取之前检索过的内容,却发现其他事务插入了新数据,这种现象就是幻读
3.4 事务隔离级别
事务4种隔离级别
- 未提交读(Read Uncommitted)
- 提交读(Read Committed)
- 可重复读(Repeateable Read)
- 串行化(Serializable)
innodb引擎对隔离级别支持
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read Uncommitted | 是 | 是 | 是 |
Read Committed | 否 | 是 | 是 |
Repeateable Read | 否 | 否 | 否 |
Read Committed | 否 | 是 | 是 |
Repeateable Read | 否 | 否 | 否 |
Serializable | 是 | 是 | 是 |
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,该事务等待锁释放
意向锁(IS,IX)是Innodb数据操作之前自动加上的,不需要用户干预
意向锁意义:当事务想去进行锁表时,可以先判断意向锁是否存在,存在时则可快速返回该表不能启用表锁
3.5 对行锁算法进行分析
InnoDB行锁是通过给索引上加上索引项加锁来实现的。
因此在使用InnoDB锁的时候有几个注意事项
- 只有通过索引条件检索的数据InnoDB才能使用行级锁,否则,InnoDB将使用表锁
- InnoDB行级锁是针对索引加的锁,不是针对记录加的锁,所以访问不同行的记录,但是如果使用相同的索引键,是会出现锁冲突的
- 当表中多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,不论主键索引,唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁
- 条件中使用索引字段,但是是否使用索引来检索数据需要通过explain来检查是否真正使用索引,因为MySQL会对sql执行进行优化
- 临建锁 Next-key Locks
临建锁(Next-key):是InnoDB默认行级锁实现,锁住记录+区间(左开右闭),当sql执行按照索引进行数据的检索时,查询条件为范围查询(between and,<>等)并有数据命中则此时sql语句加上的锁为next-key locks,锁住索引的区间(左开右闭)+记录
临建锁=记录锁+间隙锁
例如数据库中有四条记录,会被划分为5个区间,(负无穷大,1],(1,4],(4,7],(7,10],(10,正无穷大)
例1:如果在事务中执行sql语句:select * from user where id >5 and id <9 for update;
锁住区间:(4,7],(7,10]
原因是查询到了id=7行,锁住当前区间和下一区间
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘8’,‘8’,‘8’);将会阻塞 - 间隙锁 gap Locks
gap只有在RR事务隔离级别中存在,当记录不存在,临建锁会退化为gap锁
gap锁:范围查询或者等值查询且记录不存在
例如数据库中有四条记录,会被划分为5个区间,(负无穷大,1],(1,4],(4,7],(7,10],(10,正无穷大)
例1:如果在事务中执行sql语句:select * from user where id >4 and id <6 for update;
锁住区间:(4,7)
原因查询记录不存在
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘5’,‘5’,‘5’);将会阻塞
例2:如果在事务中执行sql语句:select * from user where id = 20 for update;
锁住区间:(10,正无穷大)
原因查询记录不存在
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘11’,‘11’,‘11’);将会阻塞 - 记录锁 record Locks
当使用索引条件为精准匹配,退化为record锁
record lock:等值查询,精确匹配
例如数据库中有四条记录,会被划分为5个区间,(负无穷大,1],(1,4],(4,7],(7,10],(10,正无穷大)
例1:如果在事务中执行sql语句:select * from user where id =4 for update;
锁住区间:4
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘5’,‘5’,‘5’);将会成功
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘3’,‘3’,‘3’);将会成功
例2:如果在事务中执行select * from user where id in (4,7) for update;
锁住区间:4和7
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘5’,‘5’,‘5’);将会成功
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘3’,‘3’,‘3’);将会成功
如果是针对in中既有记录能查到,也有查不到的场景分析,这个等价于记录锁+间隙锁
例1:如果在事务中执行sql语句:select * from user where id in (4,5) for update;
锁住区间:4和(4,7]
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘5’,‘5’,‘5’);将会阻塞
此时如果在另外事务中再执行INSERT INTO user (id, name, comment) VALUES (‘3’,‘3’,‘3’);将会成功
从上面也可以看出间隙锁可以防止出现幻读,主要通过两个方面来实现:
(1)防止间隙内有新数据被插入
(2)防止已存在的数据,更新成间隙内的数据
3.6 InnoDB锁使用情况
获取InnoDB行锁争用情况
如果Innodb_row_lock_waits和Innodb_row_lock_time_avg的值比较高,表示锁争用情况比较严重
3.7 一致性非锁定读(MVCC)
一致性的非锁定读是指InnoDB存储引擎通过行多版本控制的方法来读取当前执行时间数据库中行的数据。
如果读取的行正在执行delete或update操作,这是读取操作不会因此等待行上锁的释放,此时会获取读取行的一个快照数据。
快照数据其实就是当前行数据之间的历史版本,每行记录可能有多个版本,这种并发控制称为多版本控制系统(MVCC)
非锁定读机制极大提高了数据库的并发性,在InnoDB存储引擎的默认的读取方式,即读取不会占用和等待表上的锁。
在事务隔离级别提交读(Read Committed)和可重复读(Repeateable Read)下,InnoDB存储引擎使用非锁定的一致性读。
在提交读(Read Committed)事务隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据;在可重复读(Repeateable Read)事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。
RR隔离级别的示例演示
数据库中源数据
通过 select @@tx_isolation;可以查询现在数据库的隔离级别
示例流程
innodb引擎对隔离级别支持
编号 | 会话1 | 会话12 |
---|---|---|
1 | begin; | 是 |
2 | update user set name=“222” where id =1; | |
3 | begin; | |
4 | select * from user where id =1; | |
5 | select * from user where id =1; | |
6 | commit; | |
7 | select * from user where id =1; | |
8 | commit; |
分析下执行结果
第四行查询结果,可以看出是老数据
第五行查询结果,可以看出是新数据
第七行查询结果,依然是老数据