我,卷王!

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: MySQL 里面表级别的锁有这几种:• 表锁;• 元数据锁(MDL);• 意向锁;• AUTO-INC 锁;

大家好,我是小林。

昨天是七夕节嘛,晚上陪女朋友吃饭去啦,然后回来肝文的时候,写着写着发现已经过晚上 12 点了,本来今天这篇是想昨天发的,可惜没赶着。卷吧,我过节都不忘记肝文,哈哈。不过,也没关系,我早上起来重新补了一些东西,文章更完善了些。这次,来说说 MySQL 的锁,主要是 Q&A 的形式,看起来会比较轻松。在 MySQL 里,根据加锁的范围,可以分为全局锁、表级锁和行锁三类。不多 BB 了,发车!

19.png


全局锁


全局锁是怎么用的?

要使用全局锁,则要执行这条命:

flush tables with read lock

执行后,整个数据库就处于只读状态了,这时其他线程执行以下操作,都会被阻塞:

  • 对数据的增删查改操作,比如 select、insert、delete、update等语句;
  • 对表结构的更改操作,比如 alter table、drop table 等语句。

如果要释放全局锁,则要执行这条命令:

unlock tables

当然,当会话断开了,全局锁会被自动释放。

全局锁应用场景是什么?

全局锁主要应用于做全库逻辑备份,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。举个例子大家就知道了。在全库逻辑备份期间,假设不加全局锁的场景,看看会出现什么意外的情况。如果在全库逻辑备份期间,有用户购买了一件商品,一般购买商品的业务逻辑是会涉及到多张数据库表的更细,比如在用户表更新该用户的余额,然后在商品表更新被购买的商品的库存。那么,有可能出现这样的顺序:

  1. 先备份了用户表的数据;
  2. 然后有用户发起了购买商品的操作;
  3. 接着再备份商品表的数据。

也就是在备份用户表和商品表之间,有用户购买了商品。这种情况下,备份的结果是用户表中该用户的余额并没有扣除,反而商品表中该商品的库存被减少了,如果后面用这个备份文件恢复数据库数据的话,用户钱没少,而库存少了,等于用户白嫖了一件商品。所以,在全库逻辑备份期间,加上全局锁,就不会出现上面这种情况了。

加全局锁又会带来什么缺点呢?

加上全局锁,意味着整个数据库都是只读状态。那么如果数据库里有很多数据,备份就会花费很多的时间,关键是备份期间,业务只能读数据,而不能更新数据,这样会造成业务停滞。

既然备份数据库数据的时候,使用全局锁会影响业务,那有什么其他方式可以避免?

有的,如果数据库的引擎支持的事务支持可重复读的隔离级别,那么在备份数据库之前先开启事务,会先创建 Read View,然后整个事务执行期间都在用这个 Read View,而且由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作。因为在可重复读的隔离级别下,即使其他事务更新了表的数据,也不会影响备份数据库时的 Read View,这就是事务四大特性中的隔离性,这样备份期间备份的数据一直是在开启事务时的数据。备份数据库的工具是 mysqldump,在使用 mysqldump 时加上 –single-transaction 参数的时候,就会在备份数据库之前先开启事务。这种方法只适用于支持「可重复读隔离级别的事务」的存储引擎。InnoDB 存储引擎默认的事务隔离级别正是可重复读,因此可以采用这种方式来备份数据库。但是,对于 MyISAM 这种不支持事务的引擎,在备份数据库时就要使用全局锁的方法。


表级锁


MySQL 表级锁有哪些?具体怎么用的。

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

  • 表锁;
  • 元数据锁(MDL);
  • 意向锁;
  • AUTO-INC 锁;


表锁


先来说说表锁。如果我们想对学生表(t_student)加表锁,可以使用下面的命令:

//表级别的共享锁,也就是读锁;
lock tables t_student read;
//表级别的独占锁,也就是写锁;
lock tables t_stuent wirte;

需要注意的是,表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。也就是说如果本线程对学生表加了「共享表锁」,那么本线程接下来如果要对学生表执行写操作的语句,是会被阻塞的,当然其他线程对学生表进行写操作时也会被阻塞,直到锁被释放。要释放表锁,可以使用下面这条命令,会释放当前会话的所有表锁:

unlock tables

另外,当会话退出后,也会释放所有表锁。不过尽量避免在使用 InnoDB 引擎的表使用表锁,因为表锁的颗粒度太大,会影响并发性能,InnoDB 牛逼的地方在于实现了颗粒度更细的行级锁


元数据锁


再来说说元数据锁(MDL)。我们不需要显示的使用 MDL,因为当我们对数据库表进行操作时,会自动给这个表加上 MDL:

  • 对一张表进行 CRUD 操作时,加的是 MDL 读锁
  • 对一张表做结构变更操作的时候,加的是 MDL 写锁

MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。当有线程在执行 select 语句( 加 MDL 读锁)的期间,如果有其他线程要更改该表的结构( 申请 MDL 写锁),那么将会被阻塞,直到执行完 select 语句( 释放 MDL 读锁)。反之,当有线程对表结构进行变更( 加 MDL 写锁)的期间,如果有其他线程执行了 CRUD 操作( 申请 MDL 读锁),那么就会被阻塞,直到表结构变更完成( 释放 MDL 写锁)。

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

MDL 是在事务提交后才会释放,这意味着事务执行期间,MDL 是一直持有的。那如果数据库有一个长事务(所谓的长事务,就是开启了事务,但是一直还没提交),那在对表结构做变更操作的时候,可能会发生意想不到的事情,比如下面这个顺序的场景:

  1. 首先,线程 A 先启用了事务(但是一直不提交),然后执行一条 select 语句,此时就先对该表加上 MDL 读锁;
  2. 然后,线程 B 也执行了同样的 select 语句,此时并不会阻塞,因为「读读」并不冲突;
  3. 接着,线程 C 修改了表字段,此时由于线程 A 的事务并没有提交,也就是 MDL 读锁还在占用着,这时线程 C 就无法申请到 MDL 写锁,就会被阻塞,

那么在线程 C 阻塞后,后续有对该表的 select 语句,就都会被阻塞,如果此时有大量该表的 select 语句的请求到来,就会有大量的线程被阻塞住,这时数据库的线程很快就会爆满了。

为什么线程 C 因为申请不到 MDL 写锁,而导致后续的申请读锁的查询操作也会被阻塞?

这是因为申请 MDL 锁的操作会形成一个队列,队列中写锁获取优先级高于读锁,一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作。所以为了能安全的对表结构进行变更,在对表结构变更前,先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更。


意向锁


接着,说说意向锁

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

也就是,当执行插入、更新、删除操作,需要先对表加上「意向共享锁」,然后对该记录加独占锁。而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。不过,select 也是可以对记录加共享锁和独占锁的,具体方式如下:

//先在表上加上意向共享锁,然后对读取的记录加独占锁
select ... lock in share mode;
//先表上加上意向独占锁,然后对读取的记录加独占锁
select ... for update;

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(lock tables … read)和独占表锁(lock tables … write)发生冲突。表锁和行锁是满足读读共享、读写互斥、写写互斥的。如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。那么有了「意向锁」,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。所以,意向锁的目的是为了快速判断表里是否有记录被加锁


AUTO-INC 锁


最后,说说 AUTO-INC 锁。在为某个字段声明 AUTO_INCREMENT 属性时,之后可以在插入数据时,可以不指定该字段的值,数据库会自动给该字段赋值递增的值,这主要是通过 AUTO-INC 锁实现的。AUTO-INC 锁是特殊的表锁机制,锁不是再一个事务提交后才释放,而是再执行完插入语句后就会立即释放在插入数据时,会加一个表级别的 AUTO-INC 锁,然后为被 AUTO_INCREMENT 修饰的字段赋值递增的值,等插入语句执行完成后,才会把 AUTO-INC 锁释放掉。那么,一个事务在持有 AUTO-INC 锁的过程中,其他事务的如果要向该表插入语句都会被阻塞,从而保证插入数据时,被 AUTO_INCREMENT 修饰的字段的值是连续递增的。但是, AUTO-INC 锁再对大量数据进行插入的时候,会影响插入性能,因为另一个事务中的插入会被阻塞。因此, 在 MySQL 5.1.22 版本开始,InnoDB 存储引擎提供了一种轻量级的锁来实现自增。一样也是在插入数据的时候,会为被 AUTO_INCREMENT 修饰的字段加上轻量级锁,然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁。InnoDB 存储引擎提供了个 innodb_autoinc_lock_mode 的系统变量,是用来控制选择用 AUTO-INC 锁,还是轻量级的锁。

  • 当 innodb_autoinc_lock_mode = 0,就采用 AUTO-INC 锁;
  • 当 innodb_autoinc_lock_mode = 2,就采用轻量级锁;
  • 当 innodb_autoinc_lock_mode = 1,这个是默认值,两种锁混着用,如果能够确定插入记录的数量就采用轻量级锁,不确定时就采用 AUTO-INC 锁。

不过,当 innodb_autoinc_lock_mode = 2 是性能最高的方式,但是会带来一定的问题。因为并发插入的存在,在每次插入时,自增长的值可能不是连续的,这在有主从赋值的场景中是不安全的


行级锁有哪些?


InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。行级锁的类型主要有三类:

  • Record Lock,记录锁,也就是仅仅把一条记录锁上;
  • Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身;
  • Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。

前面也提到,普通的 select 语句是不会对记录加锁的,如果要在查询时对记录加行锁,可以使用下面这两个方式:

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


上面这两条语句必须再一个事务中,当事务提交了,锁就会被释放,因此在使用这两条语句的时候,要加上 begin、start transaction 或者 set autocommit = 0。那具体跟在哪些纪录上加锁,就跟具体的 select 语句有关系了,比较复杂,这个留到下篇再讲啦。参考资料:《MySQL技术内幕:innodb》、《MySQL实战45讲》、《从根儿上理解MySQL》。下次见啦~

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
JavaScript 前端开发 程序员
珍藏多年的神网站都捐了,你需要的我都有,有了这些网站工作生活不用愁(二)
珍藏多年的神网站都捐了,你需要的我都有,有了这些网站工作生活不用愁(二)
164 0
|
SQL 数据可视化 搜索推荐
咚咚咚,穷人版生产力工具,好用到飞起
咚咚咚,穷人版生产力工具,好用到飞起
|
前端开发 安全 决策智能
博弈论(四)——产品经理绝地求生
博弈论(四)——产品经理绝地求生
121 0
回首2022,烟火如常,布衣剩饭,啥也没干,年终总结,蹈海难酬
开篇明义,2022年,我啥也没干,或者说的更准确一些,啥也没干成,没有什么值得拿出来凡尔赛一下的事情,或者可以满足一下虚荣心的成就,300多个日夜里,就是日复一日的起床、上班、讲课、下班、吃饭、睡觉。有什么可总结的呢?
回首2022,烟火如常,布衣剩饭,啥也没干,年终总结,蹈海难酬
|
6月前
|
人工智能 达摩院 小程序
大咖与小白的日常:一键生成漫画风头像
AI作画火得一塌糊涂,但是好像常常画得很离谱?不如来试试阿里云视觉智能开放平台的人物动漫化API,一键生成一个属于自己的、独一无二的漫画风头像。
142 0
大咖与小白的日常:一键生成漫画风头像
|
Web App开发 Windows
推荐5款让你相见恨晚的神级软件,把把直击心灵
今天来给大家推荐5款良心软件,每款都是经过时间检验的精品,用起来让你的工作效率提升飞快,各个都让你觉得相见恨晚!
257 0
推荐5款让你相见恨晚的神级软件,把把直击心灵
|
机器学习/深度学习
学霸、学神OR开挂
我们学习知识 好比武侠世界里的人修炼武功一般 有人天赋异禀、骨骼清奇 是天生的练武奇才——学神 有人天资平庸,但通过后天的孜孜不倦 终成一代大侠——学霸 还有人一路奇遇不断,屡获高人指点 成为绝世高手——外挂玩家
学霸、学神OR开挂
端午还在“修锁”?你是真的卷王!(一)
哈喽,大家好,我是指北君。 今天为你带来的是 ReentrantLock 公平锁与非公平锁的源码分析,它是 Java 并发包下的一个 java.util.concurrent.locks 实现类,实现了 Lock 接口和 Serializable 接口。
端午还在“修锁”?你是真的卷王!(一)
端午还在“修锁”?你是真的卷王!(二)
今天为你带来的是 ReentrantLock 公平锁与非公平锁的源码分析,它是 Java 并发包下的一个 java.util.concurrent.locks 实现类,实现了 Lock 接口和 Serializable 接口。
端午还在“修锁”?你是真的卷王!(二)
|
传感器 数据采集 监控
机器狗的烦恼:网红练习生容易,商业实习生好难
好比临近毕业正在找工作的实习生,波士顿动力的产品具备了基础理论知识,还缺少实践经验。