【MySQL进阶之路 | 高级篇】锁的概述以及读写并发问题

事务的隔离性是由锁来实现的。

1. 概述

锁是计算机协调多个进程或线程并发访问某一资源的机制。在程序开发中会存在很多线程同步的问题,当多个线程并发访问某个数据的时候,尤其是针对一些敏感的数据(如订单,金额等),我们就需要保证这个数据在任何时刻最多只有一个线程访问,保证数据的完整性和一致性。在开发过程中加锁是为了保证数据的一致性,这个思想在数据库领域同样重要。

在数据库中,除传统的计算资源(如CPU,RAM,I/O等)的争用外,数据也是一种供用户共享的资源。为了保证数据的一致性,需要对并发操作进行控制,因此产生了锁。同时锁机制也为实现MySQL的各个隔离级别分别提供了保证。锁冲突也是影响数据库的并发访问性能的一个重要因素。所以锁对数据库而言显得尤为重要,也会更加复杂。

2. MySQL并发事务访问相同记录

并发事务访问相同记录的情况大致分为三种:读-读,写-写,读-写/写-读。

2.1 读-读情况

读-读情况,即并发事务相继读取相同的记录。读取操作本身不会对记录有任何影响,并不会引起什么问题,所以允许这种情况发生。

2.2 写-写情况

该种情况,即并发事务相继对相同的记录做出改动。

在这种情况下会发生脏写的问题,任何一种隔离级别都不允许这种情况的发生。所以在多个未提交事务相继对一条记录做改动时,需要让它们排队执行,这个排队的过程其实是通过锁来实现的。这个所谓的锁其实是一个内存中的结构,在事务执行前本身是没有锁的,也就是说一开始是没有锁结构和记录进行关联的。如图:

当一个事务想对这条记录做改动时,首先会看看内存中有没有与这条记录关联的锁结构,当没有时就会在内存中生成一个锁结构与之关联。比如,事务T1要对这条记录做改动,就需要生成一个锁结构与之相关。

在锁结构里有很多信息,为了简化理解,只把两个比较重要的属性拿了出来:

  • trx信息:代表这个锁结构是哪个事务生成的。
  • is_waiting:代表当前事务是否在等待。

当事务T1改动了这条记录后,就生成了一个锁结构与该记录关联。因为之前没有别的事务为这条记录加锁,所以is_waiting属性是false,我们把这个场景为称之为获取锁成功,或者加锁成功。然后就可以继续执行操作了。

在事务T1提交之前,另一个事务T2也想对记录做改动,那么先看看有没有锁结构与之关联,发现有个锁结构与之关联后,然后也生成了一个锁结构与这条记录关联,不过锁结构的is_waiting属性值为true,表示当前事务需要等待,我们把这个场景称为获取锁失败,或者加锁失败。如图:

在事务T1提交后,就会把该事务生成的锁结构释放掉,然后看看有没有别的事务在等待获取锁,发现了事务T2还在等待获取锁,所以把事务T2对应的锁结构的is_waiting属性设置为false,然后把该事务对应的线程唤醒,让它继续执行,此时事务T2就算获取到锁了。如图:

小结:

  • 不加锁,意思就是不需要在内存中生成对应的锁结构,可以直接执行操作。
  • 获取锁就是在内存中生成了锁结构,而且锁结构的is_waiting属性为false,也就是事务可以继续执行操作。
  • 获取锁失败,或者加锁失败,或者没有获取到锁,意思就是在内存中生成了对应的锁结构,不过锁结构的is_waiting属性为true,也就是事务需要等待,不可以继续执行操作。

2.3 读-写或写-读情况

读-写或写-读情况,即一个事务进行读取操作,另一个进行改动操作。这种情况可能发生脏读,不可重复读,幻读的问题。

各个数据库厂商对sql标准的支持力度不一样,比如MySQL在REPEATABLE READ隔离级别上已经解决了幻读的问题。

2.4 并发问题的解决方案

怎么解决脏读,不可重复读,幻读这些问题呢?其实有两种可选的解决方案。

方案一:读操作利用多版本并发控制MVCC,写操作进行加锁。、

所谓的MVCC,就是生成一个ReadView,通过ReadView找到符合条件的记录版本(历史版本由undo日志构建)。查询语句只能读到在生成ReadView之前已经提交事务所做的更改,在生成ReadView之前未提交的事务或者之后才开启的事务所做的更改是看不到的。而写操作肯定针对的是最新版本的记录,读记录的历史版本和改动记录的最新版本本身并不冲突,也就是采用了MVCC,读-写操作并不冲突。

普通的select语句在READ COMMITTED和REPEATABLE READ隔离级别下会使用到MVCC读取记录。

  • 在READ COMMITTED隔离级别下,一个事务在执行过程中每次执行select操作时都会生成一个ReadView,ReadView的存在本身就保证了事务不可以读取到未提交的事务所做的更改,也就是避免了脏读现象。
  • 在REPEATABLE READ隔离级别下,一个事务在执行过程中只有第一次执行select操作才会生成一个ReadView,之后的select操作都复用这个ReadView,这样就避免了不可重复读和幻读的问题。

方案2:读-写操作都采用加锁的方式。

如果我们的一些业务场景不允许读取记录的旧版本,而是每次都必须读取记录的最新版本。比如,在银行存款的事务中,你需要先把账户的余额读出来,然后将其加上本次存款的数额,最后再写入数据库中。在将账户余额读取出来后,就不想让别的事务再访问该余额,直到本次存款事务执行完成,其他事务才可以访问账户的余额。这样在读取记录的时候就需要对其进行加锁操作,这样也意味着读操作和写操作也像写写操作那样排队执行。

  • 脏读的产生是因为当前事务读取了另一个未提交的事务写的一条记录。如果另一个事务在写记录的时候就给这条记录加锁,那么当前事务就无法继续读取该记录了,所以也就不会有脏读的问题产生。
  • 不可重复读的产生是因为当前事务先读取一条记录,另一个事务对该记录做了改动后并提交之后,当前事务再次读取时会获得不同的值,如果在当前事务读取记录时就给该记录加锁,那么另一个事务就无法修改该记录,自然也不会发生可重复读。
  • 幻读的问题的产生是因为当前事务读取了一个范围的记录,然后另外的事务向该范围的事务插入了一些新记录,当前事务再次读取该范围的记录的时候发现了新插入的记录。采用加锁的方式解决幻读问题有一定的麻烦,因为当前事务在第一次读取记录时的幻影记录并不存在,所以读取的时候加锁有点尴尬。

一般情况下,我们当然愿意采用MVCC来解决读-写操作并发执行问题,但业务在某些特殊情况下,要求必须采用加锁的方式执行。

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

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

相关文章

Qt背景与环境搭建

目录 ​编辑 一、Qt背景 1.行业岗位介绍 2.什么是Qt 3.Qt的发展史 4.Qt支持的平台 5.Qt的版本和优点 5.1 版本 5.2 优点 6.Qt的应用场景 7.Qt 的成功案例 8.Qt 发展前景 二、环境搭建 1.Qt 的开发工具概述 2.Qt SDK 的下载和安装 2.1 Qt SDK 的下载 ​编辑 2…

AI软件测试|人工智能测试中对抗样本生成攻略

从医疗诊断、自动驾驶到智能家居,人工智能技术为各个行业领域带来无限可能的同时,挑战也日益显现。特别是在人工智能安全领域,随着恶意攻击和数据欺骗的不断演变,确保AI系统的安全性和可靠性成为亟需解决的重要问题,对…

构建现代化农业产业服务平台的系统架构

随着全球农业产业的发展和技术的进步,农业生产管理面临着越来越复杂的挑战和机遇。建立一个现代化的农业产业服务平台系统架构,不仅能够提高农业生产效率和管理水平,还能促进农民收入增长和可持续发展。本文将探讨如何设计和实施这样一个系统…

【计算机网络】期末实验答辩

注意事项: 1)每位同学要在下面做过的实验列表中选取三个实验进行答辩准备,并将自己的姓名,学号以及三个实验序号填入共享文档"1(2)班答辩名单"中。 2)在答辩当日每位同学由老师在表…

C# 使用pythonnet 迁入 python 初始化错误解决办法

pythonnet 从 3.0 版本开始,必须设置Runtime.PythonDLL属性或环境变量 例如: string pathToVirtualEnv ".\\envs\\pythonnetTest"; Runtime.PythonDLL Path.Combine(pathToVirtualEnv, "python39.dll"); PythonEngine.PythonHom…

vue路由跳转时改变路由参数组件不渲染问题【已解决】

效果展示 解决 router路由为了组件复用,防止组件的频繁销毁与创建,在遇到跳转的路由不一致才会进行重新渲染,路径参数变了他是不会管的,只会改变this.$route对象而已 就这个东西/:xxx 我们可以写一个watch监视this.$route对象。…

SQL语句(以MySQL为例)——单表、多表查询

笛卡尔积(或交叉连接): 笛卡尔乘积是一个数学运算。假设我有两个集合 X 和 Y,那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合,也就是第一个对象来自于 X,第二个对象来自于 Y 的所有可能。组合的个数即为两个集合中…

python爬虫-事件触发机制

今天想爬取一些政策,从政策服务 (smejs.cn) 这个网址爬取,html源码找不到链接地址,通过浏览器的开发者工具,点击以下红框 分析预览可知想要的链接地址的id有了,进行地址拼接就行 点击标头可以看到请求后端服务器的api地…

GraphHopper路径规划导航(Android源码调试运行)

本文主要记录在运行graphhopper安卓版路径规划导航源码的步骤和遇到的问题。 成功运行了程序,但是路劲规划一直不成功,问题一开始是服务地址,后来又是key的问题,在这个项目中涉及到了graphhopper、mapbox、mapilion的key&#xff…

html+css+js 实现马赛克背景按钮

前言:哈喽,大家好,今天给大家分享htmlcss 绚丽效果!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 文…

前端练习<HtmlCSS>——照片墙(附完整代码及实现效果)

这个小练习也来源于b站up小K师兄,大家可以通过下面的链接学习哦~up讲的非常详细。 纯CSS写一个简单酷炫的照片墙效果~ 先看一下这个照片墙的效果: 1.鼠标没有放到图片上时,照片同比例,每张照片都有倒影的效果。 2.然…

生成式AI和LLM的革命:Transformer架构

近年来,随着一篇名为“Attention is All You Need”论文的出现,自然语言处理(NLP)领域经历了一场巨大的变革。2017年,在谷歌和多伦多大学发表了这篇论文后,Transformer架构出现了。这一架构不仅显著提升了N…

【BUG】已解决:ERROR: Failed building wheel for jupyter-nbextensions-configurator

ERROR: Failed building wheel for jupyter-nbextensions-configurator 目录 ERROR: Failed building wheel for jupyter-nbextensions-configurator 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页,我…

第 3 章 数据和 C

目录 3.1 示例程序 3.1.1 程序中的新元素 3.2 变量与常量数据 3.3 数据: 数据类型关键字 3.3.1 整数和浮点数 3.3.2 整数 3.3.3 浮点数 3.4 C 语言基本数据类型 3.4.1 int 类型 1. 声明 int 变量 2. 初始化变量 3. int 类型常量 4. 打印 int 值 5. 八进制和十六进…

Linux I2C驱动程序

Linux I2C驱动 前言 主要目的记录一下我在野火的i.MX6ULL mini开发板上面编写了一个Linux I2C设备驱动去驱动SSD1306 OLED屏幕的开发过程。源码用的是野火提供的内核源码。 SSD1306 OLED和I2C接口 这块OLED屏幕大家应该都比较熟悉了,小巧迷你,DIY很好…

习题2.24

没啥好说, 先看解释器给出的结果 说起来也简单,这是一个多层的列表,第一个元素是1 第二个元素是(2 ( 3 4)) 第二层第一个是2 第二个是 (3 4) 第三层第一个是 3 第二个…

stm32入门-----ADC模数转换器(理论篇——上)

目录 前言 一、ADC模数转换器 1.简介 2.ADC的结构 3.输入通道 4.转换模式 5.触发控制 6.数据对齐 7.转换时间 8.校准 前言 本期就开始学习ADC模数转换器,这个大家在学习51单片机的时候应该就接触过了,实际上就是把模拟电压转换为…

YOLOv5改进 | 卷积模块 | 即插即用的递归门控卷积gnConv

秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 专栏目录: 《YOLOv5入门 改…

认识到自己的无知,需要一定的智慧

知识本是补药,却被变成毒药——“新无知”的三种表现 今天,一个人如果把评书、演义、宫廷剧当作真实历史,把当年从教科书上学来的过时理论当做“观世界”的金科玉律,并以此嘲笑别人“无知”的,属于典型的“新古人”—…

线程的同步和优先级

文章目录 前言一、优先级和同步各是什么?二、使用方法 1.线程的优先级2.线程的同步总结 前言 线程不仅可以实现代码的单线程运行和并发运行,在线程安全的情况下,还可以实现共同处理相同的系统资源。该篇文章还介绍了开发者如何设置线程运行的…