Login
网站首页 > 文章中心 > 其它

MySQL 中的锁有哪些类型,MySQL 中加锁的原则

作者:小编 更新时间:2023-08-16 11:10:26 浏览量:302人看过

锁的类型

全局锁

缺点

适用范围

表级锁

表锁

元数据锁

意向锁

自增锁

行锁

Record Lock

Gap Lock

Next-Key Lock

插入意向锁

加锁的原则

①.、主键等值查询

总结

参考

MySQL 中的锁理解

锁的类型

MySQL 找那个根据加锁的范围,大致可以分成全局锁,表级锁和行级锁.

全局锁

全局锁,就是对整个数据库加锁.

加锁


flush tables with read lock


解锁


unlock tables


全局锁会让整个库处于只读状态,之后所有的更新操作都会被阻塞:

数据更新语句(数据的增删改);

数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句.

如果对主库加锁,那么执行期间就不能执行更新,业务基本上就停摆了;

如果对从库加锁,那么执行期间,从库就不能执行主库同步过来的 binlog,会导致主从延迟.

全局锁的典型使用场景是,做全库逻辑备份.也就是把整库每个表都select出来存成文本.

不过为什么要在备份的时候加锁,不加锁的话,备份系统备份的得到的库不是一个逻辑时间点,这个视图是逻辑不一致的.

官方自带的逻辑备份工具是 mysqldump.当 mysqldump 使用参数 –single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图.而由于 MVCC 的支持,这个过程中数据是可以正常更新的.

对于 MyISAM 这种不支持事务的引擎,mysqldump 工具就不能用了,所以 全局锁 虽然缺点很多,但是还是有存在的必要.

表级锁

MySQL 中的表级别的锁包括:表锁,元数据锁(meta data lock,MDL).

比如 InnoDB 中的意向锁和自增锁(AUTO-INC Locks)也都是表级别的锁.

下面来一一分析下

表锁,就是会锁定整张表,它是 MySQL 中最基本的锁策略,并不依赖于存储引擎,被大部分的 MySQL 引擎支持,MyISAM 和 InnoDB 都支持表级锁,由于表级锁一次会将整个表锁定,所以可以很好的避免死锁问题.当然,锁的粒度大所带来最大的负面影响就是出现锁资源争用的概率也会最高,导致并发率大打折扣.


// 表级别的共享锁,也就是读锁
lock tables t1 read

// 表级别的排它锁,也就是写锁   
lock tables t1 write


释放锁


unlock tables


表锁除了会限制其它线程的读写,还会限制当前线程此时此刻呢的操作.

如果一个线程加了表级别的读锁,其他线程对该表的写操作,都会被阻塞,同时当前线程此时此刻呢对该表的写入操作也不能执行,会报错当前表有一个读锁,直到表锁的读锁被释放.

MDL(metadata lock) 元数据也是表级别的锁.

MDL 锁主要使用来维护表元数据的数据一致性,MDL 不需要显式使用,在访问一个表的时候会被自动加上,避免在进行读取或者写入的时候,其它线程对数据表做出修改,造成写入或者读取的结果异常.

读锁之间不互斥,所以呢你可以有多个线程同时对一张表增删改查;

读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性.所以呢,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行.

给一个表加字段会可能会导致整个库崩掉,为什么呢?

MySQL 中的锁有哪些类型,MySQL 中加锁的原则

来分析下上面的栗子

①.、session A 首先启动事务,使用了一个查询,因为是查询,所以对 user 表会加一个 MDL 读锁;

MDL 锁会需要等到事务提交的时候才会被释放,这样在给表加字段的时候遇到一个长事务,就可能会导致数据表崩掉.

如何安全的对数据表添加字段呢?

设置 alter table 语句中的等待时间,如果在指定时间没有拿到 MDL 写锁,直接退出,不至于长时间阻塞后面的业务操作.失败,就后面多尝试几次.

InnoDB 存储引擎支持多粒度锁,这种锁定允许事务在行级别上的锁和表级别上的锁同时存在.这种锁就是意向锁.

意向锁有两种:

①.、意向共享锁:在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」;

意向锁的作用?

意向锁是放置在资源层次结构的一个级别上的锁,以保护较低级别资源上的共享锁或排它锁.

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,同时意向锁之间也不会发生冲突.只会和表级别的共享锁和表级独占锁发生冲突.

意向锁是 InnoDB 自动加的,不需要用户干预.这样一个事务在表中一些记录加独占锁,InnoDB 就会自动加表级别的意向锁独占锁,这样其他的事务如果对该表加表级别的独占锁,就不用遍历表里面的记录,通过表级别的意向锁直接就能判断当前事务是够会被阻塞了.

简单的讲就是意向锁是为了快速判断,表里面是否有记录被加锁.

自增锁(AUTO-INC)是一种表级锁,专门针对插入 AUTO_INCREMENT 类型的列.可以该列的值,数据库会自动赋值自增的值,这主要就是通过自增锁实现的.一般会在主键中设置 AUTO_INCREMENT.

自增锁(AUTO-INC)采用的是一种特殊的表锁机制,为了提高插入的性能,锁不是在一个事务完成后才释放,而是在完成是自增长值插入的 SQL 语句后立即释放.

通过 innodb_autoinc_lock_mode 来控制锁的类型.

首先来看下 binlog 的格式

binlog 有三种格式:

①.、Statement(Statement-Based Replication,SBR):每一条会修改数据的 SQL 都会记录在 binlog 中,里面记录的是执行的 SQL;

Statement 模式只记录执行的 SQL,不需要记录每一行数据的变化,所以呢极大的减少了 binlog 的日志量,避免了大量的 IO 操作,提升了系统的性能.

正是由于 Statement 模式只记录 SQL,而如果一些 SQL 中包含了函数,那么可能会出现执行结果不一致的情况.

比如说 uuid() 函数,每次执行的时候都会生成一个随机字符串,在 master 中记录了 uuid,当同步到 slave 之后,再次执行,就获取到另外一个结果了.

所以使用 Statement 格式会出现一些数据一致性问题.

Row 格式的日志内容会非常清楚的记录下每一行数据修改的细节,这样就不会出现 Statement 中存在的那种数据无法被正常复制的情况.

比如一个修改,满足条件的数据有 100 行,则会把这 100 行数据详细记录在 binlog 中.当然此时,binlog 文件的内容要比第一种多很多.

不过 Row 格式也有一个很大的问题,那就是日志量太大了,特别是批量 update、整表 delete、alter 表等操作,由于要记录每一行数据的变化,此时会产生大量的日志,大量的日志也会带来 IO 性能问题.

在 Mixed 模式下,系统会自动判断该用 Statement 还是 Row:一般的语句修改使用 Statement 格式保存 binlog;对于一些 Statement 无法准确完成主从复制的操作,则采用 Row 格式保存 binlog.


CREATE TABLE ◆t◆ (
  ◆id◆ int(11) NOT NULL AUTO_INCREMENT,
  ◆c◆ int(11) DEFAULT NULL,
  ◆d◆ int(11) DEFAULT NULL,
  PRIMARY KEY (◆id◆),
  UNIQUE KEY ◆c◆ (◆c◆)
) ENGINE=InnoDB;


分析下上面语句的执行

此时此刻呢 session A 和 session B 在相同的时刻写入数据到表 t 中.

那么就可能出现下面的情况

当 binlog_format=statement 的时候在来看下 binlog 是如何同步从库的数据.

对于普通的 insert 语句里面包含多个 value 值,即使 innodb_autoinc_lock_mode 设置为 1,也不会等语句执行完成才释放锁.因为这类语句在申请自增 id 的时候,是可以精确计算出需要多少个 id 的,然后一次性申请,申请完成后锁就可以释放了.

对于批量的数据插入,类似 insert ... select、replace ... select和 load data 语句.这种是不能这样操作的,因为不知道预先要申请多少个 ID.

批量的数据插入,如果一个个的申请 id,不仅速度很慢,同时也会影响插入的性能,这肯定是不合适的.

所以呢,对于批量插入数据的语句,MySQL有一个批量申请自增id的策略:

①.、语句执行过程中,第一次申请自增id,会分配1个;


insert into t values(null, 1,1);
insert into t values(null, 2,2);
insert into t values(null, 3,3);
insert into t values(null, 4,4);
create table t2 like t;
insert into t2(c,d) select c,d from t;
insert into t2 values(null, 5,5);


所以总结下来数据插入,主键 ID 不连续的情况大概有下面几种:

①.、事务回滚,事务在执行过程中出错,主键冲突,或者主动发生回滚,会导致已经申请的自增 ID 被弃用;

行锁

MySQL 的行锁是在引擎层由各个引擎自己实现的,但并不是所有的引擎都支持行锁的,比如 MyISAM 引擎就不支持行锁,InnoDB 是支持行锁的,这也是 MyISAM 被 InnoDB 替代的重要原因之一.

下面主要来介绍下 InnoDB 中的行锁.

行锁主要有下面三类:

①.、Record Lock,记录锁,也就是仅仅把一条记录锁上;

Record Lock 记录锁,这个很好理解,比如事务 A 更新了一行,而这时候事务 B 也更新了同一行,则必须等待事务 A 的操作完成才能更新.

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放.

所以当事务中需要锁多个行,要把最可能造成锁冲突.最可能影响并发度的锁尽量往后放.

同时记录锁还分成读锁和写锁:

共享锁(S锁)也叫读锁,满足读读共享,读写互斥.

独占锁(X锁)也叫写锁,满足写写互斥、读写互斥.

如果一个事务 A 对一条数据加了读锁,那么另外一个事务 B 如果同样也加了读锁,这两个事务不会互斥,都能正常读取,如果事务 B 加的是写锁,那么事务 B 就需要等待事务 A 的读锁释放之后,才能操作加锁.

如果一个事务 A 对一条数据加了写锁,那么其他的事务对这条数据加锁,无论是读锁还是写锁都需要等待事务 A 释放才能继续加锁.

如何加锁


//对读取的记录加共享锁
select ... lock in share mode;

//对读取的记录加独占锁
select ... for update;


不过需要注意的是当一条记录被加了记录锁,其它事务如果只是简单的查询,没有使用当前读,那么是不会被阻塞的,因为会通过 MVCC 找到当前可读取的版本数据,直接返回数据即可.

间隙锁(Gap Lock)是 Innodb 在可重复读提交下为了解决幻读问题时引入的锁机制.

幻读的问题存在是因为新增或者更新操作,这时如果进行范围查询的时候(加锁查询),会出现不一致的问题,这时使用普通的行锁已经没有办法满足要求,需要对一定范围内的数据进行加锁,间隙锁就是解决这类问题的.

MySQL 中的锁有哪些类型,MySQL 中加锁的原则

间隙锁之间是兼容的,两个事务之间可以共同持有包含相同间隙的间隙锁,同时也不存在互斥关系,间隙锁的目的只是为了解决幻读问题而提出的.

Next-Key Lock,是 Record Lock ◆ Gap Lock 的组合,锁定一个范围,并且锁定记录本身.

MySQL 中的锁有哪些类型,MySQL 中加锁的原则

插入意向锁是一种间隙锁形式的意向锁,在真正执行 INSERT 操作之前设置.

一个事务在数据插入的是时候会判断插入的位置是否加了被其它的事务加了间隙锁或临键锁.如果有的话,就会阻塞,直到间隙锁或临键锁被释放才能执行后面的插入.

因为插入意向锁也是一种意向锁,意向锁只是表示一种意向,所以插入意向锁之间不会互相冲突,多个插入操作同时插入同一个 gap 时,无需互相等待.

加锁的原则

分析完了 MySQL 中几个主要的锁,再来看下这几个锁在 MySQL 中的使用.

加锁原则:

①.、加锁的对象是索引,加锁的基本单位是 next-key lock,是前开后闭区间;

加锁优化:

①.、索引中的等值查询,如果加锁的对象是唯一索引,这时候锁就从 next-key lock,变成行锁了,因为只需要锁住对应的一行就行了;

同时,唯一索引上的范围查询会访问到不满足条件的第一个值为止.

下面来几个栗子来具体的分析下


CREATE TABLE ◆t◆ (
◆id◆ int(11) NOT NULL,
◆c◆ int(11) DEFAULT NULL,
◆d◆ int(11) DEFAULT NULL,
PRIMARY KEY (◆id◆),
KEY ◆c◆ (◆c◆)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);


可以看到 session B 中的插入操作被阻塞了,下面来分析下

session A:

session B:

session C:

session C 因为 id = 10 这行数据是存在的,并且 id 为主键索引.根据上面的加锁原则,首先加锁的基本单位是 next-key lock,索引中的等值查询,如果加锁的对象是唯一索引,这时候锁就从 next-key lock,变成行锁了.

可以看到 session C 被阻塞了,下面来分析下

这样加锁的范围就是 (0,10)

只有访问到的对象才会加锁,session A 的查询使用覆盖索引,并不需要访问主键索引,所以主键索引上上的查询不会被阻塞.

session C 和 session B=D:

两个插入操作,都会加插入意向锁,因为间隙 (0,10) 被 session A 锁住了,所以插入操作就会被阻塞了.

例如下面的栗子


select * from t where id=10 for update;  

select * from t where id>=10 and id<11 for update;


上面这两个查询,看起来是等价的,其实他们加锁的方式是不同的,下面来分析下

=10 and id <11 for update

session A

=10 and c <11 for update

假定目前表中的数据见下文,有两条 c=10 的数据.

MySQL 中的锁有哪些类型,MySQL 中加锁的原则

来看下下面的查询栗子

begin select * from t where c=10 for update

查询的条件是 c=10,上面的图示可以看到 c=10 的书有两条;

MySQL 中的锁有哪些类型,MySQL 中加锁的原则

所以业务中如果加入 limit 条件,能够减小锁的范围.

MySQL 中的锁有哪些类型,MySQL 中加锁的原则

总结

锁大概分成三类 全局锁,表级锁和行锁.

参考

以上就是土嘎嘎小编为大家整理的MySQL 中的锁有哪些类型,MySQL 中加锁的原则相关主题介绍,如果您觉得小编更新的文章只要能对粉丝们有用,就是我们最大的鼓励和动力,不要忘记讲本站分享给您身边的朋友哦!!

版权声明:倡导尊重与保护知识产权。未经许可,任何人不得复制、转载、或以其他方式使用本站《原创》内容,违者将追究其法律责任。本站文章内容,部分图片来源于网络,如有侵权,请联系我们修改或者删除处理。

编辑推荐

热门文章