你一定能看懂的数据库事务和事务特性实现原理

news2024/12/31 5:07:02

一。概念

事务 是数据库执行原子操作的基本单位。一个事务中的多个修改,则要么全部成功执行,要么全部不执行。

关于事务的 MYSQL 官网的解释

Transactions are atomic units of work that can be *committed* or *rolled back*. When a transaction makes multiple changes to the database, either all the changes succeed when the transaction is committed, or all the changes are undone when the transaction is rolled back.

一句话概括 事务的四大特性:

  • 原子性:多个变更操作,要么全做,要么全不做(基于undo log实现事务回滚)
  • 持久性:数据修改是永久的,即便系统故障也不会丢失(基于data和redo log实现故障后恢复)
  • 隔离性:保证多事务并发时不相互干扰(可以调整不同的隔离级别,基于锁、MVCC的并发控制实现)
  • 一致性:事务过程中要么全用旧值,要么全用新值,不允许新旧值混合(基于原子性和隔离性实现)

二。四大特性(ACID)

关于ACID的 MYSQL 官网的解释

Transactions are *atomic* units of work that can be *committed* or *rolled back*. When a transaction makes multiple changes to the database, either all the changes succeed when the transaction is committed, or all the changes are undone when the transaction is rolled back.

The database remains in a consistent state at all times — after each commit or rollback, and while transactions are in progress. If related data is being updated across multiple tables, queries see either all old values or all new values, not a mix of old and new values.

Transactions are protected (isolated) from each other while they are in progress; they cannot interfere with each other or see each other’s uncommitted data. This isolation is achieved through the *locking* mechanism. Experienced users can adjust the *isolation level*, trading off less protection in favor of increased performance and *concurrency*, when they can be sure that the transactions really do not interfere with each other.

The results of transactions are durable: once a commit operation succeeds, the changes made by that transaction are safe from power failures, system crashes, race conditions, or other potential dangers that many non-database applications are vulnerable to. Durability typically involves writing to disk storage, with a certain amount of redundancy to protect against power failures or software crashes during write operations. (In InnoDB, the *doublewrite buffer* assists with durability.)

1. 原子性(A)

一个事务(transaction)中的所有操作,要么全做,要么全不做,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

a.如何实现原子性?

开始执行事务操作时(beigin),数据库底层开始undo日志记录。在做每一个会对数据产生影响的地方,undo日志中都会记录下修改前的数据,例如:

  • 执行delete一条数据的时候,需要记录这条数据的信息,发生回滚的时候,重新插入这条旧数据

  • 执行update一条数据的时候,需要记录之前的旧值,发生回滚的时候,将记录还原为旧值

  • 执行insert一条数据的时候,需要这条记录的主键,发生回滚的时候,根据主键执行delete操作

当事务成功结束后(commit),则会清空undo日志,等待下一次事务执行。

2.持久性(D)

事务一旦提交成功,对数据的修改就是永久的,即便系统故障也不会丢失。

a.持久性要解决什么问题?

在MySQL这类的非内存数据库中,表数据是持久化在磁盘文件,同时为了提高效率,处理中的数据会加载在内存中。此外,内存中修改的数据不会实时回写磁盘,而是根据MYSQL的更新策略回写到磁盘文件。因此,事务提交成功,并不能保证数据能持久化保存在磁盘文件。需要在考虑在事务提交完后,数据还未写会磁盘文件时,发生系统崩溃或者软件崩溃导致内存数据丢失的情况。

b. 如何实现持久性?

在MYSQL中使用Redo log解决上面的问题。

当做数据修改的时候,不仅在内存中操作,还会在Redo log中记录这次操作。当事务提交的时候,会将Redo log日志回写redo日志文件。如果数据库崩溃,则会将Redo log中的内容恢复到数据库中,再根据undo log和bin log内容决定回滚数据还是提交数据。

c.为什么不直接写到data文件?

为什么要通过redo来实现持久化,而不直接写把此次修改的数据持久化到date文件?(非标准,个人观点)

  1. 磁盘数据文件缓存到内存时,不是对一条条数据进行组织而是按照实际块进行缓存
  2. 同一个数据块可能有多个事务正在使用,不能随时将数据块回写到数据库,需要加锁或者判断状态
  3. 一个事务很可能不仅仅修改一个表,如果同时修改多个表则可能同时回写很多个内存块

而持久化redo log则只需要向redo文件追加一条事务的redo记录即可。

2.一致性(C)

在事务开始之前和事务结束以后,数据库的完整性没有被破坏。即:写入的数据必须完全符合所有的预设规则,包含数据的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

a.如何实现一致性

基于原子性和隔离性实现

3.隔离性(I)

事务在进行过程中应该相互隔离,保证多个事务间不能相互干扰,即不能看到对方未提交的数据。

3.1 并发问题

实现隔离性目的是为了解决多个事务并发时的一些问题,下面是关于并发引发的几类问题总结

3.1.1 脏读

事务A读取了事务B修改的数据,若事务B发生回滚,事务A依然使用回滚的数据,称 事务A 脏读。

时间事务A事务B
T1开始事务A(Data=500)
T2开始事务B(Data=500)
T3修改数据(Data=1000)
T4读取数据(Data=1000)
T5回滚事务(Data=500)
T6使用Data=1000继续事务

注意:T4时刻,事务A读到的是未提交的数据(Data=1000)

3.1.2 脏写

事务B修改一条数据还未提交,事务A修改同一条数据未提交,事务B回滚(数据变成事务B修改前的数据),事务A提交。称 事务A 脏写(即:写入的不是事务A想要的数据)。

时间事务A事务B
T1开始事务A(Data=500)
T2修改数据(Data=2000)
T3开始事务B(Data=500)
T4修改数据(Data=1000)
T5回滚事务(Data=500)
T6提交事务(写入Data=500)
3.1.3 不可重复读

事务A读取数据后,事务B修改并提交同一个数据,事务A再次读取数据。此时事务A两次读数据不一致。称 事务A 不可重复读。

时间事务A事务B
T1开始事务A(Data=500)
T2读取数据(Data=500)
T3开始事务B(Data=500)
T4修改数据(Data=1000)
T5提交事务(Data=1000)
T6读取数据(Data=1000)
3.1.4 幻读

事务A读取了两次数据,在这两次的读取过程中事务B增删记录,事务A的两次读取出来的集合不一样。称 事务A 幻读(和不可重复读的些微区别在于:这里是集合操作,可以是读/写,比如统计记录条数、全表修改字段)

时间事务A事务B
T1开始事务A(100条数据)
T2统计数据(100条数据)
T3开始事务B(100条数据)
T4插入数据(新增10条数据)
T5提交事务(110条数据)
T6统计数据(110条数据)
3.1.5 丢失更新
A. 第一类丢失更新(回滚丢失,Lost update)

事务A期间,事务B对数据进行了更新并提交;事务A发生回滚,覆盖了事务B已经提交的数据

时间事务A事务B
T1开始事务A(Data=500)
T2读取数据(Data=500)
T3开始事务B(Data=500)
T4修改数据(Data=1000)
T5提交事务(Data=1000)
T6回滚事务(Data=500)
B. 第二类丢失更新(覆盖丢失/两次更新问题,Second lost update)

事务A期间,事务B对数据进行更新并提交;在事务A提交之后,覆盖了事务B已经提交的数据。

时间事务A事务B
T1开始事务A(Data=500)
T2读取数据(Data=500)
T3开始事务B(Data=500)
T4修改数据(Data=1000)
T5提交事务(Data=1000)
T6修改数据(Data=600)
T7提交(Data=600)
3.1.6 小结

上面的每种并发问题之间都有一些细微的差别。

  • 脏读:读到脏的数据(回滚了的数据)
  • 脏写:写入脏的数据(被回滚还原的数据)
  • 不可重复读:第一次读到修改前数据,第二次读到修改后数据(两次读数据不一致)
  • 幻读:第一次操作修改前集合数据,第二次操作修改后集合数据(两次数据结果不一致,和不可重复读区别在于一者为读数据和一者为读/写集合)
  • 丢失更新:
    • 第一类丢失更新:因为回滚,把其他事务更新的数据覆盖了
    • 第二类丢失更新:因为并发更新,把其他事务更新的数据覆盖了

3.2 解决并发问题的三种实现

3.2.1 悲观锁

理论

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,PessimisticConcurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。

事实上我们常说的悲观锁并不是一种实际的锁,而是一种并发控制的思想。悲观并发控制对于数据被修改持悲观(保守)的态度,认为数据被外界访问时,必然(较大概率)会产生冲突,所以在数据处理的过程中都采用加锁的方式来保证对资源的独占。

实现流程

  1. 开始事务后,按照操作类型给需要加锁的数据申请加某一类锁:例如共享行锁等
  2. 加锁成功则继续后面的操作,如果数据已经被加了其他的锁,而且和现在要加的锁冲突,则会加锁失败(例如已经加了排他锁),此时需等待其他的锁释放(可能出现死锁)
  3. 完成事务后释放所加的锁

优缺点

  • 优点:

    悲观并发控制采取的是保守策略:“先取锁,成功了才访问数据”,这保证了数据获取和修改都是有序进行的,因此适合在写多读少的环境中使用。当然使用悲观锁无法维持非常高的性能,但是在乐观锁也无法提供更好的性能前提下,悲观锁却可以做到保证数据的安全性。

  • 缺点:

    由于需要加锁,而且可能面临锁冲突甚至死锁的问题,悲观并发控制增加了系统的额外开销,降低了系统的效率,同时也会降低了系统的并行性。

3.2.2 乐观锁

理论

在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,OptimisticConcurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。

乐观观锁同样不是一种实际的锁,是一种并发控制的思想。乐观并发控制对数据修改持乐观态度,认为即使在并发环境中,外界对数据的操作一般是不会造成冲突,所以并不会去加锁,而是在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,则让返回冲突信息,让用户决定如何去做下一步,比如说重试或者回滚。

实现方式

CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。实现非阻塞同步的方案称为“无锁编程算法”( Non-blocking algorithm)。

乐观锁的实现就类似于上面的过程,主要有以下几种方式:

  1. 版本号标记:在表中新增一个字段:version,用于保存版本号。获取数据的时候同时获取版本号,然后更新数据的时候用以下命令:updatexxx set version=version+1,… where … version=“old version” and …。这时候通过判断返回结果的影响行数是否为0来判断是否更新成功,更新失败则说明有其他请求已经更新了数据了。
  2. 时间戳标记:和版本号一样,只是通过时间戳来判断。一般来说很多数据表都会有更新时间这一个字段,通过这个字段来判断就不用再新增一个字段了。
  3. 待更新字段:如果没有时间戳字段,而且不想新增字段,那可以考虑用待更新字段来判断,因为更新数据一般都会发生变化,那更新前可以拿要更新的字段的旧值和数据库的现值进行比对,没有变化则更新。
  4. 所有字段标记:数据表所有字段都用来判断。这种相当于就、不仅仅对某几个字段做加锁了,而是对整个数据行加锁,只要本行数据发生变化,就不进行更新。

优缺点

  • 优点:

    乐观并发控制没有实际加锁,所以没有额外开销,也不错出现死锁问题,适用于读多写少的并发场景,因为没有额外开销,所以能极大提高数据库的性能。

  • 缺点:

    乐观并发控制不适合于写多读少的并发场景下,因为会出现很多的写冲突,导致数据写入要多次等待重试,在这种情况下,其开销实际上是比悲观锁更高的。而且乐观锁的业务逻辑比悲观锁要更为复杂,业务逻辑上要考虑到失败,等待重试的情况,而且也无法避免其他第三方系统对数据库的直接修改的情况。

3.2.3 MVCC(多版本并发控制)

3.3 四个隔离级别(隔离级别从低到高)

3.3.1 读未提交

在事务之间提供最低的隔离级别。一个事务正在读数据时,允许其他事务同时进行读写操作。这种做法虽然能提升性能,但是以不太可靠的结果为代价,比如出现脏读。

请谨慎使用此隔离级别,并注意结果可能不一致或不可再现,这取决于其他并发事务此时正在做什么。通常,具有此隔离级别的事务只执行查询,不执行插入、更新或删除操作。

参考:https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_read_uncommitted

3.3.2 读已提交

一种隔离级别,它使用一种锁定策略,在事务之间放松一些保护,以提高性能。事务不能看到其他事务中未提交的数据,但可以看到当前事务启动后另一个事务提交的数据。因此,事务永远不会看到任何坏数据,但是它看到的数据可能在一定程度上取决于其他事务的时间。

当具有此隔离级别的事务执行 UPDATE...WHERE...DELETE...WHERE... 操作,其他事务可能必须等待。的成交方式代码表

参考:https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_read_committed

3.3.3 可重复读

InnoDB 的默认隔离级别。它可以防止查询的任何行被其他事务更改,从而避免不可重复读取的场景,但不会避免虚读。它使用适度严格的锁定策略,以便事务中的所有查询看到来自同一快照的数据,即事务启动时的数据。

当具有此隔离级别的事务执行UPDATE...WHEREDELETE...WHERE...SELECT...FOR UPDATELOCK IN SHARE MODE 操作,其他事务可能必须等待。

参考:https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_repeatable_read

3.3.4 串行化

使用最保守锁定策略的隔离级别,以防止任何其他事务插入或更改由该事务读取的数据,直到该事务完成。通过这种方式,同一个查询可以在事务中反复运行,并且确保每次都检索相同的结果集。任何更改自当前事务开始以来由另一个事务提交的数据的尝试都会导致当前事务等待。

这是SQL标准指定的默认隔离级别。在实践中,这种严格程度很少出现

参考:https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_serializable

3.3.5 小结
隔离级别脏读不可重复读幻读示例
读未提交不解决不解决不解决
读已提交能解决不解决不解决
可重复读能解决能解决不解决
串行化能解决能解决能解决

参考:

  • 图文并茂讲解Mysql事务实现原理
  • MYSQL官方说明
    在这里插入图片描述

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

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

相关文章

PyTorch 深度学习实战 | DIEN 模拟兴趣演化的序列网络

01、实例:DIEN 模拟兴趣演化的序列网络 深度兴趣演化网络(Deep Interest Evolution Network,DIEN)是阿里巴巴团队在2018年推出的另一力作,比DIN 多了一个Evolution,即演化的概念。 在DIEN 模型结构上比DIN 复杂许多,但大家丝毫不用担心,我们将DIEN 拆解开来详细地说…

Unity+jenkins自动化打包(1)

一 安装Jenkins https://www.jenkins.io/download/ 官网 1) 使用 brew 安装 2) 安装完成后一般都会遇到问题 我用的是jenkins-lts 稳定版 解决办法 删除掉对应的文件夹 1 rm -rf /usr/local/Homebrew/Library/Taps/homebrew/homebrew-services 2…

内网穿透实现在外远程SQL Server数据库 - Windows环境

目录 前言 1. 本地安装配置SQL Server 2. 将本地sqlserver服务暴露至公网 2.1 本地安装cpolar内网穿透 2.2 创建隧道 3. 公网远程连接sqlserver 3.1 使用命令行远程连接sqlserver, 3.2 使用图形界面远程连接sqlserver 3.3 使用SSMS图形界面远程连接sqlserver 4. 配置…

【服务器】威联通NAS文件共享 - 搭建SFTP服务并内网穿透实现在外远程访问

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员,2024届电子信息研究生 目录 前言 1. 威联通NAS启用SFTP 2. 测试局域网访问 3. 内网穿透 3.1 威联通安装cpolar内网穿透 3.2 创建隧道 3.3 测试公网远程访问 4. 配置固定公网TCP端口地址 4.1 保留一个固定TCP…

AI绘图设计师Stable Diffusion成为生产力工具(六):制作一张庆祝五一劳动节的海报

S:AI能取代设计师么? I :至少在设计行业,目前AI扮演的主要角色还是超级工具,要顶替?除非甲方对设计效果无所畏惧~~ 预先学习: 安装webui《Windows安装Stable Diffusion WebUI及问题解决记录》。…

Spring《二》bean的实例化与生命周期

🍎道阻且长,行则将至。🍓 上一篇:Spring《一》快速入门 下一篇:Spring《三》DI依赖注入 目录 一、bean实例化🍍1.构造方法 ***2.静态工厂 *使用工厂创建对象实例化bean 3.实例工厂 ***使用示例工厂创建对象…

深度学习必备书籍——《Python深度学习 基于Pytorch》

作为一名机器学习|深度学习的博主,想和大家分享几本深度学习的书籍,让大家更快的入手深度学习,成为AI达人!今天给大家介绍的是:《Python深度学习 基于Pytorch》 文章目录 一、背景二、内容简介三、新版特色四、作者介绍…

3.26学习周报

文章目录 前言文献阅读摘要简介方法结果讨论结论 时间序列预测学习1.基础知识1.1什么是时间序列?1.2时间序列的基本任务?2.时间序列预测算法汇总LSTM学习 总结 前言 本周阅读文献《Simulate the forecast capacity of a complicated water quality mode…

【SpringBoot】| 邮箱发送验证码,你会了吗?

目录 🦁 题外话🦁 提前准备2.1 配置邮箱第三方登录2.1.1 点击设置——账户2.1.2 开启POP3/SMTP服务 2.2 添加依赖2.3 yaml配置 🦁 进入主题🦁 测试使用🦁 尾声3.1 安利一个生成验证码的工具类3.1.1 添加依赖3.1.2 编写…

Qt音视频开发32-qmedia内核回调拿图片数据

一、前言 使用qmediaplayer来打开视频并播放,默认首选会采用QVideoWidget控件来展示,优点是不用自己来绘制,一切交给了QVideoWidget控件,这样可以做到极低的CPU占用,缺点也明显,就是无法拿到每一帧的图片,很多时候我们还需要主动拿到每一帧的图片来运算做人工智能,通过…

hive之left semi join(左半连接)使用方法

目录 一、建表数据准备 二、语法 三、left semi join例子 四、left semi join、join、left join的区别 1、left semi join 2、left join 3、join 结语 一、建表数据准备 参考hive之full outer join(全连接)使用方法_IMezZ的博客-CSDN博客目录介…

【Bard】谷歌的人工智能工具—Bard初体验

文章目录 一、Bard介绍二、Bard体验1、加入Bard的候补名单2、登入Bard篇3、使用Bard篇(1)提供三种预选方式✨(2)创作生成各类文案(3)无生成图画能力(4)支持语音转文本输入✨&#xf…

AI绘图设计师Stable Diffusion成为生产力工具(五):放大并修复老照片、马赛克照片、身份证件照

S:你安装stable diffusion就是为了看小姐姐么? I :当然不是,当然是为了公司的发展谋出路~~ 预先学习: 安装webui《Windows安装Stable Diffusion WebUI及问题解决记录》。运行使用时问题《Windows使用Stable Diffusion时…

MySQL安装和配置(保姆级别和全网最详细教程)

前言 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系…

OPNET Modeler 例程——ALOHA和CSMA的性能对比

文章目录 概述一、创建 ALOHA 协议模型二、创建 CSMA 协议模型三、创建收信机进程和节点模型四、创建总线型链路模型五、创建网络模型六、查看仿真结果总结 概述 本例程以以太网为例论述总线型网络的建模方法,对数据链路层的 MAC 技术进行建模分析,并进…

【多线程】常见的锁策略

✨个人主页:bit me👇 ✨当前专栏:Java EE初阶👇 ✨每日一语:老当益壮,宁移白首之心;穷且益坚,不坠青云之志。 目 录 🏳️一. 乐观锁 vs 悲观锁🏴二. 普通的互…

【数据分析之道-NumPy(七)】numpy字符串函数

文章目录 专栏导读1、函数说明2、add()函数3、multiply()函数4、center()函数5、capitalize()函数6、title()函数7、lower()函数8、upper()函数9、split()函数10、splitlines()函数11、strip()函数12、join()函数 专栏导读 ✍ 作者简介:i阿极,CSDN Pytho…

2023-04-16 学习记录--C/C++-邂逅C/C++

一、固定格式 ⭐️ stdio的理解: abbr.标准输入输出&#xff08;standard input/output&#xff09;。 #include <stdio.h> int main() {... // 表达式return 0; }二、printf语句&#xff08;输出语句&#xff09; ⭐️ &#xff08;一&#xff09;、前提【重要】 注意&a…

小程序组件化开发

前言 随着小程序的普及&#xff0c;越来越多的开发者开始使用小程序进行开发&#xff0c;而小程序的组件化开发已经成为了一种标配的开发模式。本文将深入介绍小程序组件化开发的相关知识&#xff0c;包括组件的定义、生命周期、通信和使用等方面&#xff0c;帮助大家更好地理解…

小程序WebSocket详解

1&#xff0c;什么是WebSocket&#xff1f; WebSocket是一种用于在Web浏览器和服务器之间进行双向通信的协议&#xff0c;而小程序WebSocket是在小程序中使用WebSocket协议进行双向数据通信的一种技术。它可以在单个TCP连接上进行全双工通信&#xff0c;实现实时、高效的数据通…