女朋友问我:什么是 MySQL 的全局锁、表锁、行锁?(下)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 女朋友问我:什么是 MySQL 的全局锁、表锁、行锁?

03 表级锁


MySQL 有两种表级锁:表锁以及元数据锁(meta data lock,MDL)


3.1 表锁


表锁的语法是这样的:lock tables ... read/write,它是显式使用的,同样也是通过 unlock tables 主动释放锁;当然,客户算断开或者异常时也会释放


mysql> lock tables student read,course read;
mysql> SELECT count(1) FROM student;
mysql> SELECT count(1) FROM course;
mysql> unlock tables;


需要注意一点:lock tables 除了会限制别的线程读写以外,也限定了本线程接下来操作的对象。举个栗子:


线程 A 执行 lock tables student read,course write; 语句,其他线程读 student、读写 course 都会被阻塞。同时,线程 A 在执行 unlock tables 之后,也只能读 student、读写 course;不能访问其他表。整个表格更直观:



student 表 course 表 其他表
线程 A 读写 不允许
其他线程 阻塞 阻塞 随便


PS:在没有更细粒度的年代,表锁是最常用与处理并发的方式。但是对于 InnDB 来说,一般不使用 lock tables 控制并发,因为粒度太大了。


3.2 MDL 元数据锁


MDL 不需要我们记命令,它是隐式使用的,访问表会自动加上。它的主要作用是防止 DDL(改表结构) 和 DML(CRUD 表数据) 并发的冲突


举个栗子,线程 A 遍历查询表数据,这期间线程 B 删了表的某一列,这时 A 拿到的数据就跟表结构对不上,MySQL 不允许这种事发生,所以在 5.5 版本引入了 MDL。


它的逻辑很简单,对表进行 CRUD 操作,加 MDL 读锁;对表结构下手时,加 MDL 写锁。因此:


  • 读读不互斥,可以多线程对一张表增删改查。


  • 读写互斥、写写互斥,保证对表结构下手时只能有一个线程操作,另一个进入阻塞。


3.2.1 加个字段就搞挂数据库?


我们知道 MDL 默认是系统加的,对表结构下手时(加字段、该字段、加索引等等),需要全表扫描。对大表操作时,你肯定会选月黑凤高,系统使用人数最少时进行,以免遭投诉。


但不只是大表,有时候对小表进行操作时,也会有这样的问题。比如下面的例子:4 个 session 对表进行操作。


PS:版本是 MySQL 5.7


640.png


前提:注意,我这里的事务是手动开启和提交的。而 MDL 锁是语句开始时申请,事务提交才释放。所以,如果是自动提交就不会出现下面的问题


  • T1、T2 时刻 session A 事务启动,加个 MDL 读锁,然后执行 select 语句。注意:这时事务并没有提交;
  • T3 时刻 session B 也是读操作,可以共享 MDL 读锁,顺利执行;
  • T4 时刻 session C 不讲武德,对表执行 DDL (改表结构)操作,需要的是 MDL 写锁,所以被阻塞;
  • T5 时刻 session D 也是读操作,按道理说 session C 阻塞应该没影响。


但是 MySQL 有一个队列会根据时间先后决定哪个 Session 先执行。所以,不管是 D 还是之后的 session 都会被 C 阻塞。而恰巧 student 又是访问频率很高的表,如此这个库的线程数很快就打满了


此时,数据库完全不能读写,甚至导致宕机,在用户界面看来就是没响应了。


3.2.2 安全地更改表


相信你都看出来了,出现上面问题是因为使用了长事务(一个事务包括 session A、B、C、D 的操作)。事务一直不提交,MDL 锁就会一直被占用。


所以,遇到这种情况就要在 MySQL 的  information_schema 表中先找出长事务对应的线程,把它 kill 掉。


// MySQL 长事务请看这篇:cnblogs.com/mysqljs/p/11552646.html
// 查询事务
select * from information_schema.INNODB_TRX;


那你可能又问了。我的表就是热点表访问很高频,但我又不得不加个字段。那应该咋办呢?回想下多线程业务操作时,线程一直拿不到锁,我们是怎么处理的?


没错,就是加超时时间。比如在 alter 语句里面加个等待时间,超过了这时间还拿不到锁。也不要阻塞后面的业务查询语句,先放弃更改。之后再交由你司 DBA 重复这个过程,直到更改成功。加等待时间语句,像下面这样的:


// N 以秒为单位
ALTER TABLE tbl_name WAIT N add column ...


04 行锁


mysql 的行索是在引擎实现的,但并不是所有引擎都支持行锁,不支持行锁的引擎只能使用表锁。


行锁比较容易理解:行锁就是针对数据表中行记录的锁。比如:事务 A 先更新一行,同时事务 B 也要更新同一行,则必须等事务 A 的操作完成后才能进行更新。


4.1 两阶段提交


先举个栗子:事务 A 和 B 对 student 中的记录进行操作。


640.png


其中事务 A 先启动,在这个事务中更新两条数据;事务 B 后启动,更新 id = 1 的数据。由于 A 更新的也是 id = 1 的数据,所以事务 B 的 update 语句从事务 A 开始就会被阻塞,直到事务 A 执行 commit 之后,事务 B 才能继续执行


在事务期间,事务 A 实际上持有 id = 1 和 id = 2 这两行的行锁。如果事务 B 更新的是 id = 2 的数据,那么它阻塞的时间就是从 A 更新 id = 2 这行开始(事务 A 更新 id = 1 时,它并没有阻塞),到事务 A 提交结束,比更新 id = 1 数据阻塞的时间要短。PS:理解这句话很重要。


在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。锁的添加与释放分到两个阶段进行,之间不允许交叉加锁和释放锁。


根据这个特性,对于高并发的行记录的操作语句就可以尽可能的安排到最后面,以减少锁等待的时间,提高并发性能


举个栗子:广州长隆乐园卖票系统。卖出一张票的逻辑应该分三步:


  • 1、扣除用户账户余额
  • 2、增加长隆账户收入
  • 3、插入一条交易记录


三个操作必须是要放在同一个事务当中,那应该怎么安排它们的执行顺序呢?做个分析:


  • 用户余额表是个人的,并发度很低;
  • 长隆账户表每个用户买票都要访问,并发度最高;
  • 交易记录表是插入操作问题不大;


这时将事务步骤安排成 3、1、2 这样的顺序是最佳的。因为此时如果有别的用户买票,它的事务在顺序 1、2 并不会阻塞,而是到了顺序 3 更新长隆账户表才会引起阻塞。但它的阻塞时间是最短的


4.2 死锁


不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。


举个行锁死锁的例子:两个事物相互等待对方持有的锁。


640.png


操作开始,事务 A 持有 id = 1 的行锁,事务 B 持有 id = 2 的行锁;事务 A 想更新 id = 2 行数据,不料事务 B 已持有,事务 A 只能等待事务 B 释放 id = 2 的行锁;同理,事务 B 想更新 id = 1 行数据,不料事务 A 已持有,事务 B 只能等事务 A 释放 id = 1 的行锁


两者互相等待,一直到完犊子。这就是死锁,懂了么?


4.3 如何解决死锁?


那出现了死锁怎么办?有两个解决策略:


  • 进入等待,直到超时
  • 进行死锁检测,主动回滚某个事务


4.2.2 加入等待时间


首先是第一种:直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 设置。这个参数,默认设置的锁等待时间是 50s


在 MySQL 中,像下面这样执行即可:


// 设置等待时间
mysql> set global innodb_lock_wait_timeout = 500;


上面这个语句表示:当出现死锁以后,第一个被锁住的线程要过 500s 才会超时退出,然后其他线程才有可能继续执行。


你可能说这不解决啦?真简单。别得意,这里还有个坑。到底设置多长的过期时间合适呢?


我设置 1s 吧,有些线程可能并没有发生死锁,只是正常的等待锁。这就会造成本来正常的线程让我给干掉了。


4.2.3 死锁检测


再看第二种:死锁检测,主动回滚某个事务。MySQL 通过设置 innodb_deadlock_detect 的值决定是否开启检测,默认值是 on(开启)。


主动死锁检测在发生死锁的时候,可以快速发现并进行处理的,但是它也有额外负担。


什么负担呢?循环依赖检测,过程如下图:


新来的线程 F,被锁了后就要检查锁住 F 的线程(假设为 D)是否被锁,如果没有被锁,则没有死锁,如果被锁了,还要查看锁住线程 D 的是谁,如果是 F,那么肯定死锁了,如果不是 F(假设为 B),那么就要继续判断锁住线程 B 的是谁,一直走知道发现线程没有被锁(无死锁)或者被 F 锁住(死锁)才会终止


如果大量并发修改同一行数据,死锁检测又会怎样呢?


假设有 1000 个并发线程同时更新同一行,那么死锁检测操作就是 1000 x 1000 达到 100 万量级的。即便最终检测结果没有死锁,但这期间要消耗大量 CPU 资源。所以,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务的情况


4.2.4 解决热点行更新问题


那前面两种方案都有弊端,死锁的问题应该怎么解决呢?


一种比较依赖运气的方法就是:如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。但是这可能会影响到业务:开启死锁检测,出现死锁就回滚重试,不会影响到业务。如果关闭,可能就会大量超时,严重就会拖垮数据库。


另一种就是在服务端(消息队列或者数据库服务端)控制并发度:之所以担心死锁检测会造成额外的负担,是因为并发线程很多的时候,假设我们能在服务端做下限流,比如同一样最多只能允许 10 个线程同时修改。


一个思想:减少死锁的主要方向,就是控制访问相同资源的并发事务量


05 巨人的肩膀


  • 《高性能 MySQL》
  • time.geekbang.org/column/article/69862
  • cnblogs.com/dyh004/p/11264569.html
  • cnblogs.com/mysqljs/p/11552646.html
  • blog.csdn.net/Annie_ya/article/details/104938829
  • blog.csdn.net/u012483153/article/details/107308715


06 总结


本文详细介绍了 MySQL 的全局锁、表级锁、元数据锁以及行锁和死锁。其中全局锁撩到了应用场景、为什么备份要加全局锁?如何利用一致性视图备份以及为啥 readonly = 1 不适合用来做备份?


表级锁聊了表锁、MDL 元数据锁以及怎么利用 MDL 锁安全快速更改表结构;行锁聊了两阶段提交、死锁的定义、死锁的检测以及给怎么解决死锁,提供了两种思路。


好啦,以上就是狗哥关于 MySQL 锁的总结。感谢各技术社区大佬们的付出,尤其是极客时间,真的牛逼。如果说我看得更远,那是因为我站在你们的肩膀上。希望这篇文章对你有帮助,我们下篇文章见~

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
21小时前
|
算法 关系型数据库 MySQL
|
1天前
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL 锁
|
3天前
|
关系型数据库 MySQL 中间件
【MySQL实战笔记】07 | 行锁功过:怎么减少行锁对性能的影响?-02 死锁和死锁检测
【4月更文挑战第19天】在高并发环境下,死锁发生在多个线程间循环等待资源时,导致无限期等待。MySQL中,死锁可通过`innodb_lock_wait_timeout`参数设置超时或`innodb_deadlock_detect`开启死锁检测来解决。默认的50s超时可能不适用于在线服务,而频繁检测会消耗大量CPU。应对热点行更新引发的性能问题,可以暂时关闭死锁检测(风险是产生大量超时),控制并发度,或通过分散记录减少锁冲突,例如将数据分拆到多行以降低死锁概率。
19 1
|
7天前
|
关系型数据库 MySQL 索引
MySQL 锁机制
MySQL 锁机制
7 0
|
15天前
|
关系型数据库 MySQL 数据库
MySQL锁三部曲:临键、间隙与记录的奇妙旅程
MySQL锁三部曲:临键、间隙与记录的奇妙旅程
18 0
|
3天前
|
SQL 存储 关系型数据库
MySQL Cluster集群安装及使用
MySQL Cluster集群安装及使用
|
18天前
|
关系型数据库 MySQL 数据库
mysql卸载、下载、安装(window版本)
mysql卸载、下载、安装(window版本)
|
7天前
|
关系型数据库 MySQL 数据库
《MySQL 简易速速上手小册》第1章:MySQL 基础和安装(2024 最新版)
《MySQL 简易速速上手小册》第1章:MySQL 基础和安装(2024 最新版)
29 4
|
1月前
|
Ubuntu 关系型数据库 MySQL
Ubuntu 中apt 安装MySQL数据库
Ubuntu 中apt 安装MySQL数据库
69 0
|
1天前
|
关系型数据库 MySQL 数据安全/隐私保护
安装mysql和远程连接
安装mysql和远程连接
7 0