mysql-事务以及锁原理讲解(二)

news2024/11/14 14:13:12

1、前言

众所周知,事务和锁是mysql中非常重要功能,同时也是面试的重点和难点。本文会详细介绍事务和锁的相关概念及其实现原理,相信大家看完之后,一定会对事务和锁有更加深入的理解。

2、什么是事务

在维基百科中,对事务的定义是:事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

3、事务的四大特性

事务包含四大特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)(ACID)。

原子性(Atomicity)
原子性是指对数据库的一系列操作,要么全部成功,要么全部失败,不可能出现部分成功的情况。以转账场景为例,一个账户的余额减少,另一个账户的余额增加,这两个操作一定是同时成功或者同时失败的。

一致性(Consistency)
一致性是指数据库的完整性约束没有被破坏,在事务执行前后都是合法的数据状态。这里的一致可以表示数据库自身的约束没有被破坏,比如某些字段的唯一性约束、字段长度约束等等;还可以表示各种实际场景下的业务约束,比如上面转账操作,一个账户减少的金额和另一个
账户增加的金额一定是一样的。

隔离性(Isolation)
隔离性指的是多个事务彼此之间是完全隔离、互不干扰的。隔离性的最终目的也是为了保证一致性。

持久性(Durability)
持久性是指只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态。

4、事务的状态

根据事务所处的不同阶段,事务大致可以分为以下5个状态:

活动的(active) 当事务对应的数据库操作正在执行过程中,则该事务处于活动状态。
部分提交的(partially committed)当事务中的最后一个操作执行完成,但还未将变更刷新到磁盘时,则该事务处于部分提交状态。
失败的(failed)当事务处于活动或者部分提交状态时,由于某些错误导致事务无法继续执行,则事务处于失败状态。
中止的(aborted)当事务处于失败状态,且回滚操作执行完毕,数据恢复到事务执行之前的状态时,则该事务处于中止状态。
提交的(committed)当事务处于部分提交状态,并且将修改过的数据都同步到磁盘之后,此时该事务处于提交状态。

在这里插入图片描述

5、 事务隔离级别

前面提到过,事务必须具有隔离性。实现隔离性最简单的方式就是不允许事务并发,每个事务都排队执行,但是这种方式性能实在太差了。为了兼顾事务的隔离性和性能,事务支持不同的隔离级别。为了方便表述后续的内容,我们先建一张示例表hero。

CREATE TABLE hero (
    number INT,
    name VARCHAR(100),
    country varchar(100),
    PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;

事务并发执行遇到的问题,在事务并发执行时,如果不进行任何控制,可能会出现以下4类问题:

5.1、脏写(Dirty Write)

脏写是指一个事务修改了其它事务未提交的数据。
在这里插入图片描述
如上图,Session A和Session B各开启了一个事务,Session B中的事务先将number列为1的记录的name列更新为’关羽’,然后Session A中的事务接着又把这条number列为1的记录的name列更新为张飞。如果之后Session B中的事务进行了回滚,那么Session A中的更新也将不复存在,这种现象就称之为脏写。

5.2、脏读(Dirty Read)

脏读是指一个事务读到了其它事务未提交的数据。
在这里插入图片描述

如上图,Session A和Session B各开启了一个事务,Session B中的事务先将number列为1的记录的name列更新为’关羽’,然后Session A中的事务再去查询这条number为1的记录,如果读到列name的值为’关羽’,而Session B中的事务稍后进行了回滚,那么Session A中的事务相当于读到了一个不存在的数据,这种现象就称之为脏读。

5.3、不可重复读(Non-Repeatable Read)

不可重复读指的是在一个事务执行过程中,读取到其它事务已提交的数据,导致两次读取的结果不一致。
在这里插入图片描述
如上图,我们在Session B中提交了几个隐式事务(mysql会自动为增删改语句加事务),这些事务都修改了number列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看到最新的值,这种现象也被称之为不可重复读。

5.4、幻读(Phantom)

幻读是指的是在一个事务执行过程中,读取到了其他事务新插入数据,导致两次读取的结果不一致。
在这里插入图片描述
如上图,Session A中的事务先根据条件number > 0这个条件查询表hero,得到了name列值为’刘备’的记录;之后Session B中提交了一个隐式事务,该事务向表hero中插入了一条新记录;之后Session A中的事务再根据相同的条件number > 0查询表hero,得到的结果集中包含Session B中的事务新插入的那条记录,这种现象也被称之为幻读。不可重复读和幻读的区别在于不可重复读是读到的是其他事务修改或者删除的数据,而幻读读到的是其它事务新插入的数据。

脏写的问题太严重了,任何隔离级别都必须避免。其它无论是脏读,不可重复读,还是幻读,它们都属于数据库的读一致性的问题,都是在一个事务里面前后两次读取出现了不一致的情况。

在SQL标准中设立了4种隔离级别,用来解决上面的读一致性问题。不同的隔离级别可以解决不同的读一致性问题。

READ UNCOMMITTED:未提交读。
READ COMMITTED:已提交读。
REPEATABLE READ:可重复读。
SERIALIZABLE:串行化。
各个隔离级别下可能出现的读一致性问题如下:
在这里插入图片描述

6、MVCC

6.1、介绍

MVCC(Multi Version Concurrency Control),中文名是多版本并发控制,简单来说就是通过维护数据历史版本,从而解决并发访问情况下的读一致性问题。

版本链 在InnoDB中,每行记录实际上都包含了两个隐藏字段:事务id(trx_id)和回滚指针(roll_pointer)。

trx_id:事务id。每次修改某行记录时,都会把该事务的事务id赋值给trx_id隐藏列。 roll_pointer:回滚指针。每次修改某行记录时,都会把undo日志地址赋值给roll_pointer隐藏列。 假设hero表中只有一行记录,当时插入的事务id为80。此时,该条记录的示例图如下: mvcc1
假设之后两个事务id分别为100、200的事务对这条记录进行UPDATE操作,操作流程如下: mvcc2 由于每次变动都会先把undo日志记录下来,并用roll_pointer指向undo日志地址。因此可以认为,对该条记录的修改日志串联起来就形成了一个版本链,版本链的头节点就是当前记录最新的值。如下:

在这里插入图片描述

6.2、ReadView

如果数据库隔离级别是未提交读(READ UNCOMMITTED),那么读取版本链中最新版本的记录即可。如果是是串行化(SERIALIZABLE),事务之间是加锁执行的,不存在读不一致的问题。但是如果是已提交读(READ COMMITTED)或者可重复读(REPEATABLE READ),就需要遍历版本链中的每一条记录,判断该条记录是否对当前事务可见,直到找到为止(遍历完还没找到就说明记录不存在)。InnoDB通过ReadView实现了这个功能。ReadView中主要包含以下4个内容:
m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
creator_trx_id:表示生成该ReadView事务的事务id。
有了ReadView之后,我们可以基于以下步骤判断某个版本的记录是否对当前事务可见。
如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
在MySQL中,READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同。READ COMMITTED在每次读取数据前都会生成一个ReadView,这样就能保证每次都能读到其它事务已提交的数据。REPEATABLE READ 只在第一次读取数据时生成一个ReadView,这样就能保证后续读取的结果完全一致。

7、锁

7.1、介绍

事务并发访问同一数据资源的情况主要就分为读-读、写-写和读-写三种。

读-读 即并发事务同时访问同一行数据记录。由于两个事务都进行只读操作,不会对记录造成任何影响,因此并发读完全允许。 写-写
即并发事务同时修改同一行数据记录。这种情况下可能导致脏写问题,这是任何情况下都不允许发生的,因此只能通过加锁实现,也就是当一个事务需要对某行记录进行修改时,首先会先给这条记录加锁,如果加锁成功则继续执行,否则就排队等待,事务执行完成或回滚会自动释放锁。

读-写 即一个事务进行读取操作,另一个进行写入操作。这种情况下可能会产生脏读、不可重复读、幻读。最好的方案是读操作利用多版本并发控制(MVCC),写操作进行加锁。

7.2、锁的粒度

按锁作用的数据范围进行分类的话,锁可以分为行级锁和表级锁。
行级锁:作用在数据行上,锁的粒度比较小。 表级锁:作用在整张数据表上,锁的粒度比较大。

7.3、锁的分类

为了实现读-读之间不受影响,并且写-写、读-写之间能够相互阻塞,Mysql使用了读写锁的思路进行实现,具体来说就是分为了共享锁和排它锁:
共享锁(Shared Locks):简称S锁,在事务要读取一条记录时,需要先获取该记录的S锁。S锁可以在同一时刻被多个事务同时持有。我们可以用select … lock in share mode;的方式手工加上一把S锁。
排他锁(Exclusive Locks):简称X锁,在事务要改动一条记录时,需要先获取该记录的X锁。X锁在同一时刻最多只能被一个事务持有。X锁的加锁方式有两种,第一种是自动加锁,在对数据进行增删改的时候,都会默认加上一个X锁。还有一种是手工加锁,我们用一个FOR UPDATE给一行数据加上一个X锁。
还需要注意的一点是,如果一个事务已经持有了某行记录的S锁,另一个事务是无法为这行记录加上X锁的,反之亦然。
除了共享锁(Shared Locks)和排他锁(Exclusive Locks),Mysql还有意向锁(Intention Locks)。意向锁是由数据库自己维护的,一般来说,当我们给一行数据加上共享锁之前,数据库会自动在这张表上面加一个意向共享锁(IS锁);当我们给一行数据加上排他锁之前,数据库会自动在这张表上面加一个意向排他锁(IX锁)。意向锁可以认为是S锁和X锁在数据表上的标识,通过意向锁可以快速判断表中是否有记录被上锁,从而避免通过遍历的方式来查看表中有没有记录被上锁,提升加锁效率。例如,我们要加表级别的X锁,这时候数据表里面如果存在行级别的X锁或者S锁的,加锁就会失败,此时直接根据意向锁就能知道这张表是否有行级别的X锁或者S锁。

7.4、InnoDB中的表级锁

InnoDB中的表级锁主要包括表级别的意向共享锁(IS锁)和意向排他锁(IX锁)以及自增锁(AUTO-INC锁)。其中IS锁和IX锁在前面已经介绍过了,这里不再赘述,我们接下来重点了解一下AUTO-INC锁。
大家都知道,如果我们给某列字段加了AUTO_INCREMENT自增属性,插入的时候不需要为该字段指定值,系统会自动保证递增。系统实现这种自动给AUTO_INCREMENT修饰的列递增赋值的原理主要是两个:
AUTO-INC锁:在执行插入语句的时先加上表级别的AUTO-INC锁,插入执行完成后立即释放锁。如果我们的插入语句在执行前无法确定具体要插入多少条记录,比如INSERT … SELECT这种插入语句,一般采用AUTO-INC锁的方式。
轻量级锁:在插入语句生成AUTO_INCREMENT值时先才获取这个轻量级锁,然后在AUTO_INCREMENT值生成之后就释放轻量级锁。如果我们的插入语句在执行前就可以确定具体要插入多少条记录,那么一般采用轻量级锁的方式对AUTO_INCREMENT修饰的列进行赋值。这种方式可以避免锁定表,可以提升插入性能。
mysql默认根据实际场景自动选择加锁方式,当然也可以通过innodb_autoinc_lock_mode强制指定只使用其中一种。

7.5、InnoDB中的行级锁

前面说过,通过MVCC可以解决脏读、不可重复读、幻读这些读一致性问题,但实际上这只是解决了普通select语句的数据读取问题。事务利用MVCC进行的读取操作称之为快照读,所有普通的SELECT语句在READ COMMITTED、REPEATABLE READ隔离级别下都算是快照读。除了快照读之外,还有一种是锁定读,即在读取的时候给记录加锁,在锁定读的情况下依然要解决脏读、不可重复读、幻读的问题。由于都是在记录上加锁,这些锁都属于行级锁。

InnoDB的行锁,是通过锁住索引来实现的,如果加锁查询的时候没有使用过索引,会将整个聚簇索引都锁住,相当于锁表了。根据锁定范围的不同,行锁可以使用记录锁(Record Locks)、间隙锁(Gap Locks)和临键锁(Next-Key Locks)的方式实现。假设现在有一张表t,主键是id。我们插入了4行数据,主键值分别是 1、4、7、10。接下来我们就以聚簇索引为例,具体介绍三种形式的行锁。
记录锁(Record Locks) 所谓记录,就是指聚簇索引中真实存放的数据,比如上面的1、4、7、10都是记录。

在这里插入图片描述

显然,记录锁就是直接锁定某行记录。当我们使用唯一性的索引(包括唯一索引和聚簇索引)进行等值查询且精准匹配到一条记录时,此时就会直接将这条记录锁定。例如select * from t where id =4 for update;就会将id=4的记录锁定。
间隙锁(Gap Locks) 间隙指的是两个记录之间逻辑上尚未填入数据的部分,比如上述的(1,4)、(4,7)等。

在这里插入图片描述

同理,间隙锁就是锁定某些间隙区间的。当我们使用用等值查询或者范围查询,并且没有命中任何一个record,此时就会将对应的间隙区间锁定。例如select * from t where id =3 for update;或者select * from t where id > 1 and id < 4 for update;就会将(1,4)区间锁定。
临键锁(Next-Key Locks) 临键指的是间隙加上它右边的记录组成的左开右闭区间。比如上述的(1,4]、(4,7]等。

在这里插入图片描述

临键锁就是记录锁(Record Locks)和间隙锁(Gap Locks)的结合,即除了锁住记录本身,还要再锁住索引之间的间隙。当我们使用范围查询,并且命中了部分record记录,此时锁住的就是临键区间。注意,临键锁锁住的区间会包含最后一个record的右边的临键区间。例如select * from t where id > 5 and id <= 7 for update;会锁住(4,7]、(7,+∞)。mysql默认行锁类型就是临键锁(Next-Key Locks)。当使用唯一性索引,等值查询匹配到一条记录的时候,临键锁(Next-Key Locks)会退化成记录锁;没有匹配到任何记录的时候,退化成间隙锁。
间隙锁(Gap Locks)和临键锁(Next-Key Locks)都是用来解决幻读问题的,在已提交读(READ COMMITTED)隔离级别下,间隙锁(Gap Locks)和临键锁(Next-Key Locks)都会失效!

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

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

相关文章

7 处理多维特征的输入

文章目录课程前提知识问题引入模型改进修改神经层的增加学习能力与超参数课本代码课程来源: 链接课程文本来源借鉴&#xff1a; 链接以及&#xff08;强烈推荐&#xff09;Birandaの课程前提知识 BCELoss - Binary CrossEntropyLoss BCELoss 是CrossEntropyLoss的一个特例&am…

JavaEE day7 初识JavaScript2

函数小结 1.可以赋值给变量(其实就是被变量所指向) 2.装入容器中作为元素存在 3.在函数调用的过程中&#xff0c;函数类型作为实参 4.函数作为另一个函数的返回值 可以直接return一个函数 5.和java不同&#xff0c;JS中允许在一个函数中定义另一个函数&#xff0c;也就是嵌…

介绍一个令强迫症讨厌的小红点组件

前言 在 App 的运营中,活跃度是一个重要的指标,日活/月活……为了提高活跃度,就发明了小红点,然后让强迫症用户“没法活”。 小红点虽然很讨厌,但是为了 KPI,程序员也不得不屈从运营同学的逼迫(讨好),得想办法实现。这一篇,来介绍一个徽标(Badge)组件,能够快速搞…

解决OpenEuler系统 Minimal BASH-like line editing is supported

2023年开工解决的第一个问题~呃&#xff0c;起因是这样的&#xff0c;由于业务需要&#xff0c;修改内核参数后重新打包内核&#xff0c;然后安装内核rpm包后&#xff0c;强制关机&#xff0c;结果就出现如上界面。网上搜索后绝大部分是因为安装了双系统后找不到grub系统引导文…

ELK_Elasticsearch基础介绍

目录 一、搜索是什么&#xff1f; 二、数据库做搜索的弊端 三、全文检索、倒排索引和Lucene 四、什么是Elasticsearch 1、Elasticsearch的功能 2、Elasticsearch的使用场景 3、Elasticsearch的特点 五、elasticsearch核心概念 一、搜索是什么&#xff1f; 概念&#x…

vue2与vue3面试题之区别

目录vue2与vue3面试题之区别01&#xff1a;数据双向绑定&#xff08; proxy 替代 defineProperty&#xff09;02&#xff1a;生命周期函数的更换03&#xff1a;vue3的新特性04&#xff1a;缓存组件与更新组件05&#xff1a;ref和reactive的区别06&#xff1a;watch和watchEffec…

测试篇(五):什么是自动化测试、自动化测试分类、selenium工具、第一个自动化测试程序

目录一、什么是自动化测试二、自动化测试分类2.1 单元测试2.2 UI自动化测试三、selenium工具3.1 selenium的介绍3.2 环境部署3.3 selenium的常用方法四、第一个自动化测试用例一、什么是自动化测试 在日常生活中我们会见到&#xff0c;自动化的水龙头、无人驾驶汽车、自动化的…

Mysql,使用FIND_IN_SET()函数处理多表关联问题.

这里有 user表、teacher表&#xff0c;其中 teacher.user_ids 字段中的值是 user.id 值以英文半角逗号拼接而来。现在&#xff0c; 我们需要在查询 teacher 表数据时&#xff0c;将 user.name 的值也查询出来。使用以下的SQL语句&#xff0c;即可实现需求。SELECTGROUP_CONCAT(…

系统编程中的进程的概念No.1

引言&#xff1a; 北京时间2023/1/28&#xff0c;本小编04年1月9日出生&#xff0c;今天第一次理解到进程的概念&#xff0c;所以我们接下来就学习一下什么是进程以及和进程相关的一些知识。首先我们想要了解进程以及其相关的知识&#xff0c;我们要先理解一下其它方面的知识&…

【2】Linux基础命令

学习笔记目录 初识Linux--入门Linux基础命令--会用Linux权限管控--懂权限Linux实用操作--熟练实战软件部署--深入掌握脚本&自动化--用的更强项目实战--学到经验云平台技术--紧跟潮流 Linux的目录结构 Linux的目录结构是一个树形结构&#xff0c;没有盘符这个概念&#x…

常用算法分类

按照使用场景分类排序算法&#xff0c;如冒泡排序&#xff0c;快速排序等&#xff0c;用于将一组数据按照特定规则排序。搜索算法&#xff0c;如二分查找算法&#xff0c;深度优先搜索算法等&#xff0c;用于在一组数据中查找特定元素。图论算法&#xff0c;如最短路径算法&…

Claude的2022年终总结——关于2022和Claude的四个问题

文章目录前言1. 我算是合格的开发者了吗2. 我算是正式的游戏人了吗3. 我算是成熟的社会人了吗4. 我算是什么样的写作者呢最后前言 2022年的这个时候&#xff0c;我也是在准备着年终总结&#xff0c;只不过应公司要求&#xff0c;准备述职晋升&#xff0c;是抱着升职加薪&#…

行为型模式 - 命令模式Command

模式的定义与特点 命令模式&#xff08;Command Pattern&#xff09;&#xff0c;是将一个请求封装成一个对象&#xff0c;从而使您可以用不同的请求对客户进行参数化。命令模式是把发出命令的责任和执行命令的责任分割开&#xff0c;委派给不同的对象。命令模式允许请求的一方…

设计一个消息队列的思考点

导图所以主要考虑的点是&#xff1a;P1.1. MQ 要有基础的消息管理能力&#xff08;CRUD&#xff09;P1.2. MQ 要有产消日志P2. MQ将消息存储成功才能响应成功P3.1 MQ将消息存储 分片存储P3.2 扩容的实现思路(如何在扩容的时候更方便高效)P4.1 数据要有副本&#xff08;分片副本…

【27】C语言 | 指针进阶

目录 一、指针概念 二、字符指针 三、指针数组 四、数组指针 五、数组参数、指针参数 六、函数指针 七、函数指针数组 八、回调函数 一、指针概念 1.指针就是个变量&#xff0c;用来存放地址&#xff0c;地址唯一标识一块内存空间。2.指针的大小是固定的4/8个字节(32位平…

Java ccflow 代码

草稿规则目录概述需求&#xff1a;设计思路实现思路分析1.URL管理参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge Survive. happ…

盖子的c++小课堂——第十四讲:指针

前言 作者&#xff1a;大家好鸭&#xff0c;想必大家看到标题都有感到有一丝奇怪吧&#xff0c;其实&#xff0c;今天主要讲一些运算符 粉丝&#xff1a;啊……嗯嗯嗯 作者&#xff1a;那开始吧~~ 内存地址运算符& 粉丝&#xff1a;讲这个干嘛&#xff0c;我都会了~~ …

Jmeter场景组合测试——多个线程组的设计方案

我们绝大多数同学在使用jmeter进行性能测试时都会在一个线程组中完成测试工作&#xff0c;今天我来重点讲解一下jmeter多个线程组在测试中的应用&#xff0c;这也是关于jmeter性能测试面试过程中的进阶问题&#xff0c;希望能够帮到大家来解决工作中不同的测试需求。线程组中的…

Rust个人学习之有意思的所有权

在Rust中是没有内存垃圾回收机制(GC)的&#xff0c;那Rust是如何保障内存安全的呢&#xff1f;这就引出了“所有权”这个概念。 我们看下下面这段伪代码 let s "helloString"; t s; print(s); 在之前我们学习的语言中&#xff0c;比如C语言&#xff0c;对于上述伪…

人工智能原理复习 | 产生式系统

文章目录 一、概述二、八数码问题三、特殊的产生式系统四、一些补充CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 通过学习人工智能原理课程了解基本的人工智能问题的求解方法和原理。 一、概述 产生式系统(Production System):是构造知识型系统和建立认知模型时常用的知…