MySQL的锁

目录

全局锁

全局锁的应用场景

加全局锁的缺点

表级锁

表级锁的分类

表锁

元数据锁(MDL)

意向锁

AUTO-INC锁

行级锁

行级锁的分类

[行锁(Record Lock)](#行锁(Record Lock))

[间隙锁(Gap Lock)](#间隙锁(Gap Lock))

[临键锁(Next-Key Lock)](#临键锁(Next-Key Lock))

[插入意向锁(Insert Intention Lock)](#插入意向锁(Insert Intention Lock))


锁是计算机协调多个进程或线程并发访问某一资源的机制。在 MySQL 里,根据加锁的范围,可以分为全局锁、表级锁和行级锁三类。

全局锁

全局锁就是对整个数据库实例加锁,加锁后整个实例就只处于只读状态,后需的DML语句(增删改),DDL语句(修改表字段等等),已经更行操作的事务提交语句都会被阻塞。

开启全局锁命令如下:

bash 复制代码
flush tables with read lock;

如果要释放全局锁,则执行以下命令

bash 复制代码
unlock tables;

当然,会话断开的话,全局锁也是会释放的。

全局锁的应用场景

做全库的逻辑备份,对所有的表进行锁定,从而保证数据的完整性。这样可以在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。

加全局锁的缺点

如果在主库上进行备份,那么在备份期间都不能执行更新,业务基本上就得停摆;

那有什么办法可以解决?

备份数据库的工具是 mysqldump,在使用 mysqldump 时加上 --single-transaction 参数的时候,就会在备份数据库之前先开启事务。这种方法只适用于支持「可重复读隔离级别的事务」的存储引擎。

在innodb中我们可以在备份时加上参数 -- single- transaction参数来完成不加锁的一致性数据备份。

bash 复制代码
mysqldump --single-transaction -uroot -p test >test.sql

InnoDB 存储引擎默认的事务隔离级别正是可重复读,因此可以采用这种方式来备份数据库。

表级锁

表级锁的分类

MySQL 里面表级别的锁有这几种:

  • 表锁(表共享读锁(read lock), 表独占写锁(write lock) );
  • 元数据锁(MDL);
  • 意向锁;
  • AUTO-INC 锁;

表锁

表锁有两个:共享读锁(read lock)和 独占写锁(write lock)。

bash 复制代码
--加锁,只可以对一个表进行加锁
lock talbes 表名 read/write;

--释放锁, 客户端断开连接也会自动释放锁
unlock talbes;

举个例子:只对表A加锁了,却访问表B,这时会显示出错。尝试在一条语句中对多个表加锁,回复是成功,但是使用select查看时候出错。

bash 复制代码
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| has_key        |
| no_key         |
| user           |
+----------------+
3 rows in set (0.00 sec)

mysql> lock tables user read;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from no_key;
ERROR 1100 (HY000): Table 'no_key' was not locked with LOCK TABLES

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)


--尝试一次对多张表加锁, 回复是成功,但是在select时候显示错误
mysql> lock tables user no_key read;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user;
ERROR 1100 (HY000): Table 'user' was not locked with LOCK TABLES
mysql> select * from no_key;
ERROR 1100 (HY000): Table 'no_key' was not locked with LOCK TABLES
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

mysql> lock tables user,no_key read; 
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ',no_key read' at line 1

注意:

1.当前会话加了共享读锁后,其他会话不能进行增删改等操作的DDL/DML语句。而当前会话也不允许执行增删改等操作的DDL/DML语句。

2.当前会话加了独占写锁后,当前会话是可以进行查看和修改数据的。而其他会话不可读不可写。

备注:若不明白DDL语句是什么意思,可以查看该文章MySQL的SQL语句

不过应尽量避免在使用 InnoDB 引擎的表使用表锁,因为表锁的颗粒度过大,会影响并发性能。

InnoDB 是实现了颗粒度更细的行级锁, 也是有这个原因,innodb才是MySQL的默认引擎**。**

元数据锁(MDL)

试想在这个场景,客户A在修改表A第一个字段的值,而客户B此时在删除表A结构的第一个字段,那这时候就会出问题了。

那要如何解决这个问题,MySQL5.5中引入了MDL,英文全称是meta data lock。

首先要注意的是,该锁是系统自动控制的,无需显示使用的,在访问一张表的时候会自动加上的。这个特性是在MySQL5.5中引入的。

  • 当对一张表进行增删改查的时候,加MDL读锁(共享锁)
  • 当对表结构进行变更操作的时候,加MDL写锁(也称为排他锁,独占锁)

metadata lock是表级锁,是在server层加的,适用于所有存储引擎,其主要作用是维护表元数据的数据一致性。是为了避免DML语句和DDL冲突,保证读写的正确性。

MDL 是不需要显示调用,那它是在什么时候释放的?

MDL 是在事务提交后才会释放,这意味着事务执行期间,MDL 是一直持有的

当开启事务,查询表,另一个会话进行删该表的字段操作时候,操作3会被阻塞,一直等到操作4提交事务后才成功。

换另一种场景,开启事务,进行删除字段操作,而另一会话之后也开启事务,进行查询表操作,而这个查询却没有被阻塞。这是因为这种DDL语句,就是对表,对库有创建,删除,修改的操作语句,都是在语句前就默认自动提交事务的了,不会等到commit才提交事务的。

DDL语句会进行隐式提交,是不能再rollback的,所以大家在操作中一定要注意。

备注:关于不能rollback的语句,可以查看文章MySQL中有事务无法回滚的语句?

还有另一种情况:

3个会话,会话1开启事务,之后查询,会话2想进行DDL语句,会被堵住。会话3也进行查询,也被堵住。

这是因为申请 MDL 锁的操作会形成一个队列,队列中写锁获取优先级高于读锁,一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作。

意向锁

想象一种情况:在进行修改表的某行的时候,是对某行加行锁,那这时其他会话想加表锁,而加表锁前,需要判断该表的行是否被锁住。这时意向锁就派上用场了,就直接查看该表是否有意向锁即可。

简单来说,为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查,也是为了快速判断表里是否有记录被加锁

也就是说,当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,这个是mysql自动添加的。

select 也是可以对记录加共享锁和独占锁的 ,写select语句需要额外添加些关键词。

意向锁有两种:

  • 意向共享锁(lS) : 由语句select ... lock in share mode添加,与表锁read兼容,与表锁排它锁write互斥。
  • 意向排他锁(IX)∶由insert、update、delete、select ... for update添加,与表锁共享锁read及排它锁write互斥。

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突, 意向锁之间也不会互斥,意向锁只会和表锁(表共享锁,排它锁)互斥。

AUTO-INC锁

我们可以在表中设定一个自增的主键字段,在后续的插入中,就可有不需要指定主键的值,让数据库自动为主键赋值。这是如何实现的呢?而在并发的情况中,又如何不出现相同的的ID问题?这就需要用到AUTO-INC 锁。

起初,AUTO-INC锁 在插入数据的时候加入,此时其他的事务都无法插入,那么就能维护主键的唯一性和自增,也就是说这把表级锁,在执行插入语句的时候加锁,在执行结束后释放锁,而不是在事务结束后释放锁。

但是,大量的数据进行插入的时候,如果每次插入都需要加一把锁,那么插入的性能将会大大降低,因此,在MySQL5.1.22版本开始,它的实现换了一种方式:

使用轻量级的锁,在插入数据时,给这个字段赋值一个自增的值,然后就释放这个轻量级的锁,不需要等到整个插入语句执行结束再释放锁,它省去了执行的那段时间,只需要申领一个自增ID就释放锁。

在MySQL中,可以通过innodb_autoinc_lock_mode这个系统变量区设置自增的策略

  • 当 innodb_autoinc_lock_mode = 0,就采用 AUTO-INC 锁,语句执行结束后才释放锁;
  • 当 innodb_autoinc_lock_mode = 2,就采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放。
  • 当 innodb_autoinc_lock_mode = 1:
    • 普通 insert 语句,自增锁在申请之后就马上释放;
    • 类似 insert ... select... 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放;

该参数值为 2 是性能最高的,但是当搭配 binlog 的日志格式是 statement 一起使用的时候,在「主从复制的场景」中会发生数据不一致的问题

行级锁

InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。

InnoDB的数据是基于索引组织的,行锁是通过索引上的索引项加锁来实现的,而不是通过记录来加锁的

行级锁的分类

  • 行锁(Record Lock): 锁定单个行记录的锁,防止其他事务对此进行删除和更新操作。在RC和RR隔离级别都支持。
  • 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别中支持。
  • 临键锁(Next-Key Lock):行锁和间隙锁的组合。同时锁住该记录,并锁住数据前面的间隙Gap。在RR隔离级别支持。

行锁(Record Lock)

也称为记录锁,锁住的是一条记录。

innodb实现了两种类型行锁:共享锁(S 锁)和排它锁(X 锁)。

注意:innnodb的行锁是针对索引加的锁,要是不通过索引条件检索数据,那么innodb将对表中的所有记录加锁,此时升级为表锁。

间隙锁(Gap Lock)

目的是为了解决可重复读隔离级别下幻读的现象。

幻读现象:一个场景,在一个事务A中,先查看数据,有id为1,5,9的数据,没有id=3的这个记录。而后其他事务B添加了id=3这个记录。事务A再次查看,却查到了id=3这条数据。这是幻读。

如何解决,可以在id为(1,5)这个范围内加上锁,这个就是间隙锁。

间隙锁也是分为排它锁和互斥锁,但他们之间都是兼容共存 的。间隙锁的唯一目的是防止其他事务插入间隙,所以 两个事务的间隙锁(锁住同一范围)之间是相互兼容的,不会产生冲突。

有同学肯定会好奇:那在什么情况下会使用间隙锁?是innodb自动添加的,还是需要用户写语句时候额外添加关键字的呢?

这个就稍微复杂点,后序会出一篇文章来详细讲解。临键锁也是如此。

临键锁(Next-Key Lock)

是 Record Lock + Gap Lock 的组合,锁定一个范围,并且也锁定记录本身,比如范围(2,10],是左开右闭区间。而间隙锁就是左开右开区间。

当我们使用了范围查询 ,不仅命中了 Record 记录 ,还包含了 Gap 间隙,在这种情况下我们使用的就是临键锁 ,它也是 MySQL 里面默认的行锁算法

临键锁 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。

next-key lock 是包含间隙锁+记录锁的**,而记录锁是有互斥情况的。** 所以如果一个事务获取了 X 型(即是排他锁)的 next-key lock, 那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的

插入意向锁(Insert Intention Lock)

插入意向锁是一种特殊的间隙锁,表示插入的意向,只有在 INSERT 的时候才会有这个锁。

但是它不是锁定间隙,而是等待某个间隙。比如上面间隙锁图例子:想要插入 id = 4 的数据 事务A,但由于被间隙锁(1,5)给阻塞了,所以事务 A 会生成一个插入意向锁,表明等待这个间隙锁的释放。

注意:这个锁虽然也叫意向锁,但是和前面的表级意向锁是两个完全不同的概念。

并且插入意向锁之间不会阻塞,因为它们的目的也是只等待这个间隙被释放,所以插入意向锁之间没有冲突。

**插入意向锁只会和间隙锁或 Next-key 锁冲突。**间隙锁唯一的作用就是防止其他事务插入记录造成幻读,而正是由于在执行 INSERT 语句时需要加插入意向锁,而插入意向锁和间隙锁冲突,从而阻止了插入操作的执行。

**插入意向锁的作用?作用很鸡肋,好像是没什么作用。**它的目的不在于锁定资源防止别人访问,更像是为了遵循 MySQL 的锁代码实现而为之。

锁其实就是内存里面的一个结构,每个事务为某个记录或者间隙上锁就是创建一个锁对象来争抢资源。如果某个事务没有抢到资源,那也会生成一个锁对象,只是状态是等待的,而当拥有资源的事务释放锁之后,就会寻找正在等待当前资源的锁结构,然后选一个让它获得资源并唤醒对应的事务使之得以执行。

相关推荐
小冷coding12 小时前
【MySQL】MySQL 插入一条数据的完整流程(InnoDB 引擎)
数据库·mysql
周杰伦的稻香15 小时前
MySQL中常见的慢查询与优化
android·数据库·mysql
·云扬·17 小时前
MySQL 常见存储引擎详解及面试高频考点
数据库·mysql·面试
何以不说话18 小时前
mysql 的主从复制
运维·数据库·学习·mysql
橘子1319 小时前
MySQL库的操作(二)
数据库·mysql·oracle
·云扬·20 小时前
MySQL各版本核心特性演进与主流分支深度解析
数据库·sql·mysql
田超凡21 小时前
深入理解MySQL_6 Temporary临时表
mysql·java-ee
尽兴-1 天前
MySQL 8.0主从复制原理与实战深度解析
数据库·mysql·主从复制
YongCheng_Liang1 天前
MySQL 高级特性深度解析:从索引优化到高可用架构
运维·数据库·mysql
<花开花落>1 天前
MySQL 数据备份流程化
mysql·systemd