数据库相关学习杂记-事务

ARIES(基于语义的恢复与隔离算法)是现代数据库理论的基础。提供了解决ACID中A、I、D重要的解决思路。

基础知识

这里先复习一下关于ACID的含义以及数据库隔离级别:

ACID的含义

原子性(Atomicity): 一个事务中被视为不可分割的最小单元,整个事务中所有的操作要么全部提交成功,要么全部失败,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。

一致性(Consistency): 数据库总是从一个一致的数据状态转换成另外一个一致的状态。(比如银行转账前后,总金额是不变的。)

隔离性(Isolation): 通常来说一个事务所做的改变对其他事务来说是不可见的。

持久性(Durability): 一旦事务提交成功,事务所做的修改将会永久被保存在数据库中。

数据库隔离级别

读未提交(READ UNCOMMITED):在此级别,事务中的修改,即使没有提交,也能被其他事务可见。(事务可以读取未提交的数据,称为脏读 Dirty Read)

读已提交(READ COMMITED): 事务开始时,只能“看见”已经提交事务所做的修改。也叫不可重复读,因为有时可能两次查询的结果可能不一样(后面解释原因)。

可重复读(REPEATABLE READ): 该级别保证了再同一个事务中多次读取同样的记录结果是一样的。但是不能避免幻读,幻读是指在某个事务在读取一个范围内的记录时,另外一个事务又在该范围新增了一条记录,之前的事务再次读取该范围记录时,会产生幻行。InnoDB通过多版本控制MVCC解决了幻读的问题。可重复读是MYSQL默认的隔离级别。

串行化(SERIALIZABLE): 串行化是最高的隔离级别,它强制事务通过串行执行,避免前面的所有问题。

实现原子性和持久性

Commit Loging(Redo Log也称为重做日志)

数据需要写入磁盘等存储介质才能真正的持久化,但是写入磁盘这个操作不是原子性的。在内存中的数据一旦崩溃,数据就会存在丢失,也存在“正在写”、“写入”、“未写入”等状态。所以实现原子性持久性的最大困难在于写入磁盘。由于写入中间状态和崩溃无法避免,所以为了保证原子性和持久性,就只能采取崩溃后的补救恢复措施。这种被称为“崩溃恢复(Crash Recovery)”。那么如何补救呢?答案就是记录修改的日志信息。有了日志,修改数据就不能像之前一样直接修改,而是要先记录修改这个数据的全部信息,包括修改前的值,修改后的值,数据处于哪个内存页和磁盘块中等等,在日志记录全部成功落盘之后,写入提交记录,数据库在“看见”磁盘根据提交成功的“提交记录(Commited Record)”,才会根据日志信息对真正的数据进行修改,修改完之后,再提交一条结束修改的记录(End Record)表示数据已经完成持久化。这种实现方式被称为“提交日志(Commit Loging)”。

Commit Loging 原理                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 

假设日志写入到Commited Record,即表示事务是成功,即使在此阶段崩溃,数据库重启之后,也会根据Commit Loging中的记录对数据进行恢复,继续持久化。假设日志没有写入到Commited Record,数据库重启之后,会认为此事务是没有成功的,直接将Commit Loging中的这部分的日志信息标记为回滚就行,数据也进行了恢复。(阿里的OceanBase就是基于这个原理实现的)

Commit Loging 缺陷

所有数据都必须在Commited Record记录成功之后,才会真正的进行修改。试想一想,这个地方是不是天然被加上了“锁”,在记录Commited Record之前,不管系统资源多么空闲,不会对任何数据进行修改。这样就形成了资源的浪费,性能的降低。

Write-Ahead Loging

为了优化Commit Loging锁带来的缺陷,ARIES提出提前写日志(Write-Ahead Loging)改进方案。就是允许在事务提交之前写入变动数据。

FORCE:在事务提交之后,要求变动的数据必须同时完成写入则称为FORCE。不强制变动数据写入则称为NO-FORCE。如何理解呢,由Commit Loging的写入过程可知,由于是先记录Commit Loging的日志信息,在写入Commited Record,最后再根据Commit Loging对数据进行真正的写入。FORCE就是在执行写入Commited Record之后,立马(同时)对数据进行持久化更改。NO-FORCE则是不限制数据真正修改的时间,可以批量操作等等。从磁盘IO优化的角度来说,NO-FORCE肯定更优一点。所以现在绝大多数数据库实现策略都是采用的NO-FORCE策略。

STEAL:在事务提交前,允许数据提前写入,则称为STEAL。不允许则称为NO_STEAL。提前写入有利于空闲IO的利用,也有利于节省数据库缓存区内存。但是Steal有个缺点就是,提前写入没提交事务之前的数据,一旦事务提交失败,这部分数据是需要回滚的。

所以综上两个分类可以知道,Commit Loging的设计原则是NO-FORCE、NO-STEAL的。

Write-Ahead Loging的设计原则是,NO-FORCE、STEAL。而引入了Undo Log解决提前写入数据的回滚问题。

Undo Log(回滚日志):当变动的数据写入磁盘前,必须先记录Undo Log日志,注明修改了哪个位置的数据,从什么修改成了什么等。以便在事务回滚或者崩溃恢复的时候根据Undo Log对提前写入的数据进行回滚。

Write-Ahead Loging 崩溃恢复的流程

  • 分析阶段:  该阶段从最后一次检查点(Checkpoint: 该检查点之前的所有持久化都已经安全落盘)开始扫描日志,找出没有End Record的事务集合
  • 重做阶段(Redo): 该阶段从分析阶段查找的事务集合中筛选出Commit Record的事务,并根据Redo Log进行数据的恢复落盘,并写入End Record,移除已经处理的事务
  • 回滚阶段(Undo): 该阶段处理重做阶段中剩下的没有Commit Record记录的事务,这些事务都是需要回滚的。程序会根据Undo log 将数据进行回滚回去。

重做阶段和回滚阶段都必须设计为幂等。各个策略排队组合性能和需要的日志的排列组合图如下:

实现隔离性

隔离性保证了各个事务的读写数据互不影响。其实现原理就是加锁。

锁相关概念

写锁(Write Lock):也可以叫排他锁,只有持有写锁的事务才能对数据进行写入操作,数据加持着写锁,其他事务不能写入数据也不能施加读锁。(和Java 的读写锁一样)

读锁(Read Lock):也叫共享锁,多个事务可以对一个数据同时加读锁,但是数据一旦被加上了读锁,就不能再加写锁(即其他事务不能对此数据进行写入操作),但是仍然可以读,如果此数据之一一个事务加读锁,该事务本身可以将数据升级为写锁,然后写入数据。

范围锁(Range Lock): 对某个范围直接加排他锁,在这个范围内的数据不能被写入。注意的是范围锁是对整个范围加锁,没有获得范围锁的事务不能在这个范围内不能新增修改删除数据。以下就是加范围锁的例子,区别于id <100 的记录每条数据都加单个的读锁,假设我的id = 10的记录在数据库中不存在,单个加读锁的情况,就可以进行id=10这条数据的插入,但是范围锁不允许这样的操作。

SELECT *  FROM goods WHERE id < 100 FOR UPDATE; -- 这就是加范围锁

隔离级别实现的原理

可串行化:对所有事务的读写全部加上读写锁、范围锁,即可做到可串行化。所以可串行化也是性能最差的隔离级别

可重复读:对所有的事务的读写全部加读写锁,直到事务的完成,且不再加范围锁。对于可串行化来说,少了范围锁,那么他统计某个范围条数第一次读取和第二次读取记录的条数可能会不一样,即出现幻读。例如下面的SQL中事务1中第一次读到的数据总数和第二次读到的不一致。但是如果下面的是MySQL的InnoDB就能读到一致的数据,因为InnoDB 使用MVCC解决了幻读的问题。

SELECT count(1) FROM goods WHERE price < 100; -- 事务1
INSERT INTO goods (id,name,price) VALUES(1000,'商品1',99); -- 事务2
SELECT count(1) FROM goods WHERE price < 100; -- 事务1

读已提交:读已提交对所有事务涉及的写操作会加写锁,持续到事务完成。但是对事务涉及的读锁在查询操作完成之后就会立即释放。正因为如此,假设有两个事务,分别是事务1和事务2,事务1对数据进行查询,然后立马释放锁,事务2对相同的数进行更新操作并提交,事务1再去对相同的数据进行查询,事务1中两次查询的结果就不是一样的,这是由于读锁操作完立马释放造成的不可重复读

SELECT * FROM goods WHERE id =1; -- 事务1第一次查询,查询完就释放掉读锁
UPDATE goods SET price = 100 WHERE id=1; COMMIT; -- 事务2可以立马获取到id=1的写锁进行更新,并提交事务
SELECT * FROM goods WHERE id =1;COMMIT; -- 事务1第二次查询,查询结果就是更新之后的100,不再是之前的数据

读未提交:它只对事务所有涉及的写操作加写锁,并持续到事务完成,对读操作完全不加锁。这个就很好理解,因为读操作不需要获取锁,所以对于加了写锁的数据依旧能读,这就是所谓的脏读。读操作不需要任何锁,写锁的排他性就不起作用,所以会出现脏读。

SELECT * FROM goods WHERE id =1; -- 事务1第一次查询,不需要任何锁
UPDATE goods SET price = 100 WHERE id=1;  -- 事务2可以立马获取到id=1的写锁进行更新,还没提交事务
SELECT * FROM goods WHERE id =1;COMMIT; -- 事务1第二次查询,查询结果就是更新之后的100,因为少了加写锁的步骤,写锁的排他性就利用不了,就可以直接读事务2中还没提交的数据

MVCC 多版本并发控制(数据库的无锁优化方案)

“无锁”是指读取是不需要加锁。它的基本思路就是同时存在新老版本数据共存,以此达到读取不需要加锁的目的,类似乐观锁。

MVCC是一个行锁的变种,我的理解是他就是一个行级的乐观锁。基于事务ID是全局严格递增的数字,针对每一行数据进行修改的时候,每行记录有两个隐藏的版本号字段,一个创建版本号CREATE_VERSION、一个删除版本DELETE_VERSION

  • 新增数据时,CREATE_VERSION的版本号存当前事务的ID,DELETE_VERSION置空
  • 删除数据时,CREATE_VERSION版本号置空,DELETE_VERSION版本号存当前事务的ID
  • 修改数据时,可以认为是先删除在插入。所以先复制一份原来的数据,CREATE_VERSION版本号置为当前更新的事务ID,DELETE_VERSION版本号置空。再删除原来的数据,DELETE_VERSION版本号置为当前更新的事务ID,CREATE_VERSION置空。

这样做的好处在于,针对可重复读隔离界别的时候,查询的时候总是取CREATE_VERSION小于等于当前事务ID的记录,如果小于等于当前事务ID的数据有多个版本,就取小于等于中最新的记录即可。例如像可重复读中的举的幻读的例子:

SELECT count(1) FROM goods WHERE price < 100; -- 事务1
INSERT INTO goods (id,name,price) VALUES(1000,'商品1',99); -- 事务2
SELECT count(1) FROM goods WHERE price < 100; -- 事务1

当不存在MVCC的时候且事务隔离界别是可重复读,出现的幻读场景,代码如上所示。

第一行事务1统计的商品数量肯定是比事务1第二次统计的数量至少少1(因为还可能有其他事务对goods进行插入操作等等).原因在可重复读的实现原理中也进行了说明,因为没有加范围锁导致的。

但是如果我们现在已经有了MVCC的情况下我们来分析一下结果。

按照执行顺序,事务1在事务2之前,那我根据数据库事务ID的严格递增性,那我们假设事务1的事务ID=100,事务2的事务ID=101,那么根据上面MVCC所描述的版本号实现的数据数据应该是这样(假设没有修改和删除操作)

idnamepriceCREATE_VERSIONDELETE_VERSION
1衣服9012
2鞋子80.534
3裤子73.543
4帽子99.569

第一行事务1的第一次统计就变成了(CREATE_VERSION <= 100 是MySQL帮我们自动加上的)

SELECT count(1) FROM goods WHERE price < 100 AND CREATE_VERASION <= 100; -- 事务1

事务2执行后的结果,数据表就变成了如下所示

idnamepriceCREATE_VERSIONDELETE_VERSION
1衣服9012
2鞋子80.534
3裤子73.543
4帽子99.569
1000商品199101

那么因为事务1的ID依旧是100,第一次执行SQL依旧是

SELECT count(1) FROM goods WHERE price < 100 AND CREATE_VERASION <= 100; -- 事务1

所以两次的查询结果就是一样的,这样MVCC就解决了幻读了。

参考文献《高性能MYSQL》《凤凰架构》

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

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

相关文章

2024 java大厂面试复习总结(一)(持续更新)

10年java程序员&#xff0c;2024年正好35岁&#xff0c;2024年11月公司裁员&#xff0c;记录自己找工作时候复习的一些要点。 java基础 hashCode()与equals()的相关规定 如果两个对象相等&#xff0c;则hashcode一定也是相同的两个对象相等&#xff0c;对两个对象分别调用eq…

Python绘制太极八卦

文章目录 系列目录写在前面技术需求1. 图形绘制库的支持2. 图形绘制功能3. 参数化设计4. 绘制控制5. 数据处理6. 用户界面 完整代码代码分析1. rset() 函数2. offset() 函数3. taiji() 函数4. bagua() 函数5. 绘制过程6. 技术亮点 写在后面 系列目录 序号直达链接爱心系列1Pyth…

mfc100u.dll是什么?分享几种mfc100u.dll丢失的解决方法

mfc100u.dll 是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;属于 Microsoft Foundation Classes (MFC) 库的一部分。MFC 是微软公司开发的一套用于快速开发 Windows 应用程序的 C 类库。mfc100u.dll 文件包含了 MFC 库中一些常用的函数和类的定义&#xff0c;这…

【JavaEE】Servlet:表白墙

文章目录 一、前端二、前置知识三、代码1、后端2、前端3、总结 四、存入数据库1、引入 mysql 的依赖&#xff0c;mysql 驱动包2、创建数据库数据表3、调整上述后端代码3.1 封装数据库操作&#xff0c;和数据库建立连接3.2 调整后端代码 一、前端 <!DOCTYPE html> <ht…

WebRTC音视频同步原理与实现详解(上)

第一章、RTP时间戳与NTP时间戳 1.1 RTP时间戳 时间戳&#xff0c;用来定义媒体负载数据的采样时刻&#xff0c;从单调线性递增的时钟中获取&#xff0c;时钟的精度由 RTP 负载数据的采样频率决定。 音频和视频的采样频率是不一样的&#xff0c;一般音频的采样频率有 8KHz、…

蓝桥杯每日真题 - 第21天

题目&#xff1a;(空间) 题目描述&#xff08;12届 C&C B组A题&#xff09; 解题思路&#xff1a; 转换单位&#xff1a; 内存总大小为 256MB&#xff0c;换算为字节&#xff1a; 25610241024268,435,456字节 计算每个整数占用空间&#xff1a; 每个 32 位整数占用…

利用Python爬虫获得1688按关键字搜索商品:技术解析

在电商领域&#xff0c;1688作为中国领先的B2B电商平台&#xff0c;其商品搜索功能对于商家来说具有极高的价值。通过获取搜索结果&#xff0c;商家可以更好地了解市场趋势&#xff0c;优化产品标题&#xff0c;提高搜索排名。本文将介绍如何使用Python编写爬虫&#xff0c;以获…

三、计算机视觉_05MTCNN人脸检测

0、人脸识别流程概述 人脸识别流程包括两个主要步骤&#xff1a; Step1&#xff1a;人脸检测&#xff0c;确保我们处理的是正确的人脸区域 Step2&#xff1a;身份识别&#xff0c;确定该人脸的身份 0.1 人脸检测 人脸检测是从图像中定位人脸并抠出人脸区域的过程&#xff…

「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用

1. 谷歌浏览器安装及使用流程 1.1 准备篡改猴扩展程序包。 因为谷歌浏览器的扩展商城打不开&#xff0c;所以需要准备一个篡改猴压缩包。 其他浏览器只需打开扩展商城搜索篡改猴即可。 没有压缩包的可以进我主页下载。 也可直接点击下载&#xff1a;Chrome浏览器篡改猴(油猴…

STM32F103C8T6实时时钟RTC

目录 前言 一、RTC基本硬件结构 二、Unix时间戳 2.1 unix时间戳定义 2.2 时间戳与日历日期时间的转换 2.3 指针函数使用注意事项 ​三、RTC和BKP硬件结构 四、驱动代码解析 前言 STM32F103C8T6外部低速时钟LSE&#xff08;一般为32.768KHz&#xff09;用的引脚是PC14和PC…

【JavaEE初阶】多线程初阶下部

文章目录 前言一、volatile关键字volatile 能保证内存可见性 二、wait 和 notify2.1 wait()方法2.2 notify()方法2.3 notifyAll()方法2.4 wait 和 sleep 的对比&#xff08;面试题&#xff09; 三、多线程案例单例模式 四、总结-保证线程安全的思路五、对比线程和进程总结 前言…

【人工智能】Python在机器学习与人工智能中的应用

Python因其简洁易用、丰富的库支持以及强大的社区&#xff0c;被广泛应用于机器学习与人工智能&#xff08;AI&#xff09;领域。本教程通过实用的代码示例和讲解&#xff0c;带你从零开始掌握Python在机器学习与人工智能中的基本用法。 1. 机器学习与AI的Python生态系统 Pyth…

“iOS profile文件与私钥证书文件不匹配”总结打ipa包出现的问题

目录 文件和证书未加载或特殊字符问题 证书过期或Profile文件错误 确认开发者证书和私钥是否匹配 创建证书选择错误问题 申请苹果 AppId时勾选服务不全问题 ​总结 在上线ios平台的时候&#xff0c;在Hbuilder中打包遇见了问题&#xff0c;生成ipa文件时候&#xff0c;一…

element-ui 中el-calendar 日历插件获取显示的第一天和最后一天【原创】

需要获取el-calendar 日历组件上的第1天和最后一天。可以通过document.querySelector()方法进行获取dom元素中的值&#xff0c;这样避免计算问题。 获取的过程中主要有两个难点&#xff0c;第1个是处理上1月和下1月的数据&#xff0c;第2个是跨年的数据。 直接贴代码&#xff…

JavaScript的基础数据类型

一、JavaScript中的数组 定义 数组是一种特殊的对象&#xff0c;用于存储多个值。在JavaScript中&#xff0c;数组可以包含不同的数据类型&#xff0c;如数字、字符串、对象、甚至其他数组。数组的创建有两种常见方式&#xff1a; 字面量表示法&#xff1a;let fruits [apple…

5.5 W5500 TCP服务端与客户端

文章目录 1、TCP介绍2、W5500简介2.1 关键函数socketlistensendgetSn_RX_RSRrecv自动心跳包检测getSn_SR 1、TCP介绍 TCP 服务端&#xff1a; 创建套接字[socket]&#xff1a;服务器首先创建一个套接字&#xff0c;这是网络通信的端点。绑定套接字[bind]&#xff1a;服务器将…

Android 15 版本更新及功能介绍

Android 15版本时间戳 Android 15,代号Vanilla Ice Cream(香草冰淇淋),是当下 Android 移动操作系统的最新主要版本。 开发者预览阶段:2024年2月,谷歌发布了Android 15的第一个开发者预览版本(DP1),这标志着新系统开发的正式启动。随后,在3月和4月,谷歌又相继推出了D…

第02章_MySQL环境搭建(基础)

1. MySQL 的卸载 1.1 步骤1&#xff1a;停止 MySQL 服务 在卸载之前&#xff0c;先停止 MySQL8.0 的服务。按键盘上的 “Ctrl Alt Delete” 组合键&#xff0c;打开“任务管理器”对话 框&#xff0c;可以在“服务”列表找到“MySQL8.0” 的服务&#xff0c;如果现在“正在…

红队笔记--W1R3S、JARBAS、SickOS、Prime打靶练习记录

W1R3S(思路为主) 信息收集 首先使用nmap探测主机&#xff0c;得到192.168.190.147 接下来扫描端口&#xff0c;可以看到ports文件保存了三种格式 其中.nmap和屏幕输出的一样&#xff1b;xml这种的适合机器 nmap -sT --min-rate 10000 -p- 192.168.190.147 -oA nmapscan/ports…

学习笔记|MaxKB对接本地大模型时,选择Ollma还是vLLM?

在使用MaxKB开源知识库问答系统的过程中&#xff0c;除了对接在线大模型&#xff0c;一些用户出于资源配置、长期使用成本、安全性等多方面考虑&#xff0c;还在积极尝试通过Ollama、vLLM等模型推理框架对接本地离线大模型。而在用户实践的过程中&#xff0c;经常会对候选的模型…