MySQL加锁规则

项目编写涉及到数据持久化一般选择使用MySQL。由于时间原因,数据库使用我选择了无脑三板斧:1. 建立了索引加速查询、2. 关闭自动提交事务、3. 在需要确保原子性的数据库操作之间手动创建和提交事务

这么一看,仿佛即使是实际开发也与你此前听闻的一些MySQL相关名词:读写锁间隙锁多版本并发控制redo logbin logundo log毫不相干,在讲本文的主题之前,我先引入一个真实场景。

某次不够规范的小组开发过程中,开发成员选择测试程序的方式比较原始,大家共享一个测试数据库,各自使用测试账号进行接口的测试,这就意味着数据库中的记录在某一时刻有可能被多个事务访问,甚至在其他人测试的同时,某张数据表的结构被另一位同学修改。

多事务并发访问,反映到开发者这边,就是查询接口有时速度很慢。如果你是直接使用数据库管理工具操作数据库表数据/结构,对应的就是Navicat不时的陷入较长时间的无响应状态。

当然导致数据库访问速度变慢的原因有很多:sql语句编写不规范、数据库服务器的性能差、网络状况不佳等,但是本文所侧重的点在于探究MySQL锁机制,在其中发挥了什么作用。

相信在完成本文的阅读之后,你会明白上面的场景的发生,可能是MySQL的锁机制从中作祟。

MySQL的锁有哪几种

全局锁

MySQL可以通过显式命令对整个数据库实例加全局读锁:

此时整个数据库处于只读状态,所有数据记录的更新、数据库/表结构的改动提交都会被阻塞,这可以用于全库的数据备份。

表级锁

表锁

表锁可以通过以下显式命令实现对一个表添加读/写锁:

如果A线程为t1表添加了读锁,为t2表添加了写锁。则其他线程将只能读t1,写t1被阻塞;读/写t2都会被阻塞。而A线程在执行unlock tables之前,也只能执行读t1、读/写t2的操作。

元数据锁(metadata lock)

MDL锁不需要显式使用,在访问一个表的时候会被自动加上,并且当事务完成提交时释放。当对一个表数据做CRUD操作的时候,自动加MDL读锁;当对该表结构作出改动的时候,自动加MDL写锁。

  • 读锁之间不互斥,因此多个线程才可以同时访问一张数据表。
  • 读写锁之间、写锁之间是互斥的(被读锁占用时,加写锁的线程被阻塞/被写锁占用时,加读锁/写锁的线程都被阻塞),这也是为了确保表结构的修改和表的数据的操作不发生冲突。

这里展示一个多线程并发操作同一个数据表的案例:

这里线程B会因为线程A的事务还没有提交,而添加列的操作需要获取MDL写锁因此被阻塞,同时线程C申请MDL读锁的请求又被阻塞在了线程B申请MDL写锁的请求之后,此时表t在线程A事务提交之前,完全丧失了读写能力

或许此时你已经对于为什么多人调试程序时数据库访问不时出现卡顿有了一些自己的想法,当然这只是锁机制的冰山一角

行级锁

通过上面的讲解,我们明白了,所谓的读写锁并不是单指一个锁叫读锁/写锁,而是指不同粒度的锁有读锁和写锁两种状态,允许的并发程度也有所不同。行级锁也是如此(针对记录行的锁,锁粒度进一步缩小),行锁的存在也使得事务并发访问数据库的性能进一步的提高,并且依旧有读写锁之分,下面介绍。

但区别于全局锁和表级锁,MySQL行锁是由各个存储引擎自己实现的,并不是所有的存储引擎都支持行锁(MyISAM不支持),由于现在MySQL用户大多选择使用InnoDB存储引擎,所以本文将以InnoDB引擎为默认选择

两阶段锁协议

在InnoDB事务当中,行锁在需要的时候添加,并且直到事务提交才释放(锁的添加和释放分两个阶段进行),举个例子:

事务A(线程A)在提交之前,占有id=1这条行记录的写锁,事务B(线程B)修改同一行的操作将被阻塞。

死锁与检测

死锁原本是操作系统当中的概念,意思是多个线程都在等待其他线程释放自己需要的资源,使得这些线程陷入无限制的等待。

在这个例子当中,线程A的事务和线程B的事物分别占有id=1id=2这两条记录的写锁,使得两个线程在试图获取其他线程占用的锁资源时陷入死锁。

InnoDB存储引擎默认开启了死锁检测,每个新来的被阻塞的线程,都会主动判断是否是自己的加入导致死锁(检测逻辑就是判断自己需要的行资源是否被别的线程的事务占有),时间复杂度O(n),一旦检测到,则回滚当前线程的事务,确保其他线程可以得到执行。

这里你会发现,如果同时有多个线程修改同一条记录,一旦并发度很高,则需要消耗O(n^2)时间去完成死锁检测,就会消耗大量CPU资源在死锁检测上,而使得数据库IO的性能下降。

此时你是否又对我最初给出的小组开发时访问数据库慢的场景有了自己的思考,其实在高QPS情况下,发生死锁检测的概率是大大高于小组开发场景的

因此控制热点记录的并发访问数量,是提升数据库IO性能的重要前提。

多版本并发控制(MVCC)

上面讲述了InnoDB的update操作会占用行记录的写锁,那么你自然而然就想到,select查询操作是否就占用了行记录的读锁呢?不完全正确,这就不得不提及MySQL的InnoDB引擎的用于控制事务隔离级别的多版本并发控制机制

简言之就是每条行记录值的变化是由一个链式的结构组织的,存放在undo log文件当中,undo log在事务发生回滚的时候,用于回溯事务对行记录的修改过程。

而InnoDB存储引擎默认的事务隔离级别是可重复读(Read Repeatable),简单来说:就是当事务A启动期间,普通的select查询将无法访问到其他事务在此期间对表记录的改动。

关于多版本并发控制(MVCC)这里我没有过多深入讲解,详情给出我的另一篇文章:一文搞懂MySQL事务的隔离性如何实现|MVCC - 掘金

快照读

对于普通的查询操作,你大致了解InnoDB引擎管理的表的行记录变更是链式组织的,那么每一条记录就相当于一个个的快照,因此普通的select查询操作被称为快照读,会读取到自己可见的最近一个版本(但不一定是最新版本),快照读并不加锁(也就是没有获取读锁)。

至于具体读到哪个版本的快照,在上面链接给出的文章中有详细讲解。

当前读

这里给出了两种不同的当前读方式,当前读可以读取到undo log版本链上的最新记录,不同之处在于,第一条sql获取了id=1这条行记录的读锁(在其他事务已经持有id=1行记录的写锁时将被阻塞);第二条select查询虽然也是当前读,但是获取了id=1这条记录的写锁(在其他事物已经持有id=1行记录的读/写锁时将被阻塞)。

上面讲解死锁检测的时候我用更新语句获得了行记录的写锁,而这里,通过增加for update后缀,可以使得当前读操作也获取行记录的写锁

间隙锁

间隙锁的出现解决了幻读问题,那么先简述一下幻读的概念,以及幻读有什么问题。

幻读概述

  • InnoDB引擎的可重复读隔离级别下,普通查询是快照读,不会看到其他并发事务插入的数据,因此幻读在当前读情况下才会出现。
  • 幻读指当前读场景下,查询到了其他并发事务新插入的行(读到其他事务对行记录的修改,并不属于幻读,因为当前读就是会读取到行记录的最新版本)。

幻读的问题

这里用一张表t的操作来描述幻读带来的问题。

 

以下的分析建立在没有间隙锁的情况下(只是为了分析所作的假设):

  • 事务A的第一个sql查询c=1的记录,获得(1,1),此时添加了for update,从语义上就是希望锁住所有c=1的行记录。
  • 并且在RR隔离级别下,所有扫描到的行数据都会加行锁,因为c字段没有索引,比较c=1的操作需要全表扫描,因此事务A的第一条sql在当前读的情况下,为整张表的3条行记录都添加了写锁。
  • 此时事务B并发插入了一条(2,1)的记录,并且成功。
  • 事务A的第二个sql依旧查询c=1的记录,获得(1,1)、(2,1)两条记录,从语义上违背了第一条sql的目的。(原本打算锁定所有c=1的记录,但是突然又冒出一条记录)

这里的核心问题就在于:即使所有扫描到的行记录都加上了锁,依旧无法阻止新记录的插入(因为要插入的记录不可能提前锁定),要避免幻读,就需要将记录之间的间隙锁定——间隙锁

Gap Lock

间隙锁在可重复读隔离级别下才有效,所以本文的描述都是基于RR级别(InnoDB存储引擎事务默认隔离级别),这里给出间隙锁配合行锁工作的一些规则:

  • 所有的锁是添加在索引上的
  • 加间隙锁的基本单位是next-key lock(前开后闭区间)
  • 查找过程中访问到的记录和区间才会加锁
  • 索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁
  • 索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁
  • 唯一索引上的范围查询会访问到不满足条件的第一个值为止

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/339775.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设置若依Token过期时间

方法一:设置永不过期,有安全隐患,不建议使用 redisCache.setCacheObject(userKey, loginUser); 方法二:修改application.yml

将 OpenCV 与 Eclipse 一起使用(插件 CDT)

先决条件 两种方式,一种是直接形成项目,另一种是 CMake 先决条件 在您的工作站中安装了 Eclipse(只需要 C/C 的 CDT 插件)。您可以按照以下步骤操作: 转到 Eclipse 站点下载面向 C/C 开发人员的 Eclipse IDE。根据您…

蓝桥杯省赛无忧 STL 课件11 pair

01 pair的定义和结构 在C中&#xff0c;pair是一个模板类&#xff0c;用于表示一对值的组合&#xff0c;它位于头文件中。 pair类的定义如下: template<class T1,class T2>struct pair{T1 first;//第一个值T2 second;//第二个值// 构造函数pair();pair(const T1& X…

模拟超市商品结算系统

要求:全程一个角色(管理员即用户) (1)需要管理员注册与登录 (2)管理员登录之后&#xff0c;可以进行上架新的商品(商品名称和单价) (3)管理员登录之后&#xff0c;也可以下架商品 (4)在节假日有优惠活动,可以对其中的一些商品修改相应的单价(价格提高和价格降低都可以) (5)用户…

如何充分发挥HubSpot CRM优势,优化销售流程?

在竞争激烈的商业环境中&#xff0c;销售流程的优化对企业至关重要。HubSpot CRM作为一款全面而强大的工具&#xff0c;为企业提供了实现销售优化的完美平台。让我们深入了解如何最大程度地利用HubSpot CRM&#xff0c;提高销售效率。 1.建立清晰的销售流程 HubSpot CRM的第一…

MySQL的三种存储引擎 InnoDB、MyISAM、Memory

InnoDB 1). 介绍 InnoDB是一种兼顾高可靠性和高性能的通用存储引擎&#xff0c;在 MySQL 5.5 之后&#xff0c;InnoDB是默认的MySQL 存储引擎。 2). 特点 DML操作遵循ACID模型&#xff0c;支持事务&#xff1b; 行级锁&#xff0c;提高并发访问性能&#xff1b; 支持外键F…

爱情视频相册怎么做?2.14情人节表白/活动视频模板PR剪辑素材

美好爱情故事&#xff0c;情人节表白视频相册怎么做&#xff1f;粉色浪漫的PR情人节表白/活动视频模板剪辑素材mogrt下载。 特征&#xff1a;可编辑文字和调整颜色&#xff0c;通过智能对象替换图像&#xff0c;RGB颜色模式&#xff0c;易于自定义&#xff0c;无需插件&#xf…

【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你认识和了解Java虚拟机的特性和本质

全方位带你认识和了解Java虚拟机的特性和本质 专栏介绍前提准备面向人群知识脉络背景介绍Java虚拟机定义抽象规范多种平台实现 JVM和JRE、JDK的关系JVM实例和JVM执行引擎实例JVM的基本结构类加载子系统Bootstrap ClassLoaderExtension ClassLoaderApp ClassLoaderCustom ClassL…

Linux第24步_安装windows下的VisualStudioCode软件

Windows下的VSCode安装后&#xff0c;还需要安装gcc编译器和g编译器。 gcc&#xff1a;编译C语言程序的编译器&#xff1b; g&#xff1a;编译C代码的编译器&#xff1b; 1、在Windows下安装VSCode&#xff1b; 双击“VSCodeUserSetup-x64-1.50.1.exe”,直到安装完成。 2、…

如何在群辉NAS使用Docker搭建容器魔方并实现无公网ip远程访问

文章目录 1. 拉取容器魔方镜像2. 运行容器魔方3. 本地访问容器魔方4. 群辉安装Cpolar5. 配置容器魔方远程地址6. 远程访问测试7. 固定公网地址 本文主要介绍如何在群辉7.2版本中使用Docker安装容器魔方&#xff0c;并结合Cpolar内网穿透工具实现远程访问本地网心云容器魔方界面…

用可视化案例讲Rust编程2. 编码的核心组成:函数

从第一天学习编程&#xff0c;可能大家就听说这样的组成公式&#xff1a; 程序算法数据结构 ——该公式出自著名计算机科学家沃思(Nikiklaus Wirth) 实际上&#xff0c;程序除了以上两个主要要素之外&#xff0c;还应当采用结构化程序设计方法进行程序设计&#xff0c;并且用…

Salesforce生态系统2024年就业趋势

对于Salesforce专业人士来说&#xff0c;新一年的开始都是激动人心的。但2023年仍存在显著挑战&#xff0c;经济技术低迷导致裁员&#xff0c;以及Salesforce生态系统增长放缓等等&#xff0c;这些挑战将延续到2024年。 回顾2023年 2023年&#xff0c;Salesforce生态系统以及…