最全的分布式事务详情,它来啰~

news2024/12/28 20:21:20

我们首先得理解什么是分布式事务呢?分布式事务是指在分布式系统中,涉及多个计算机或服务器的操作序列,这些操作需要满足一致性和可靠性的要求。每个操作要么全部成功执行,要么全部回滚,以保持数据的一致性和完整性。

OK,概念讲完,我们知道一个东西的出现一定是为了解决一些问题的,那么分布式事务解决了我们什么样的难题呢,而它又是如何解决的呢?

  1. 数据一致性问题:当数据分布在不同的节点上,多个并发操作可能会导致数据不一致。分布式事务通过提供原子性和一致性,确保在分布式系统中进行的操作保持数据一致性,避免数据错误和不一致。
  2. 并发冲突问题:在分布式环境中,多个并发操作可能会相互干扰,导致数据竞争和冲突。分布式事务的隔离性能力确保并发操作之间相互隔离,防止数据冲突,确保操作的正确执行。
  3. 故障容忍和持久性问题:分布式系统中的节点可能面临故障或断电等问题。分布式事务通过持久性的特性,确保一旦事务成功提交,其结果将被永久保存,即使在系统故障或网络中断的情况下也不会丢失。

所以说,事务通过原子性、一致性、隔离性、持久性这几大特点,保障数据一致性和完整。性防止并发操作导致的数据竞争和冲突 提供故障容忍和持久性,确保操作的安全性和可靠性

如果你还对这些东西感到晕头转向,那么让我给你一个简单的场景吧,让你对它有更加深刻的理解

当你在网上购物时,使用信用卡进行支付涉及多个步骤和参与者,包括你、商家、支付网关和银行系统。在这个过程中,分布式事务的功能起到以下作用:

  1. 原子性:如果交易成功,整个支付过程要么全部成功执行,要么全部回滚。例如,如果商家没有库存或者支付网关出现问题,整个支付过程将被取消,避免了部分支付导致的数据不一致性。
  2. 一致性:分布式事务确保支付过程中的各个环节符合预定义的一致性规则。例如,商家应该从库存中减少商品数量,支付网关应该扣除相应的金额,并向银行发起付款请求。这种一致性保证了交易的正确执行,并避免了数据错误或矛盾的发生。
  3. 隔离性:分布式事务确保你的支付操作与其他用户的支付操作相互隔离,不会相互干扰。这意味着你的支付过程不会与其他用户的支付过程产生冲突,每个支付操作都独立执行,保证了数据的正确性和独立性。
  4. 持久性:一旦支付成功提交,分布式事务确保支付结果被永久保存。无论在支付过程中发生了什么,一旦支付成功,金额将从你的账户扣除并转入商家的账户。这种持久性保证了支付数据的安全存储,即使在网络故障或系统崩溃的情况下也能够恢复数据并保持一致性。

你看!一个我们日常中最常见的购物操作,都会涉及到事务的处理,所以话不多说,赶紧来了解一下事务到底是什么吧!!

事务的隔离级别

隔离级别是指在并发环境下,数据库系统为了处理事务之间可能发生的相互干扰问题而采取的一种机制。

如果想了解事务的隔离级别这个概念,让我们先来问几个问题,这些问题可能你们在生产中也会遇到

1、你是否遇到过在一个事务中读取到了另一个事务尚未提交的数据?比如发送一个消息,后端需要先更新消息明细列表数据,并且在统计表中对用户未读数的统计+1,但查询时,发现明细列表有数据了,但未读数怎么没+1呢

2、你是否曾经在同一个事务中多次读取同一数据时得到了不一致的结果?

3、你是否遇到过在同一个事务中执行了一次范围查询,然后再次执行相同查询时结果却不一样?

上面这几个问题就代表着我们可能遇到;脏读、不可重复读、幻读

那么什么又是脏读、不可重复读、幻读呢?

  • 脏读(Dirty Read):某个事务读取了另一个事务尚未提交的数据。这可能导致读取到不正确的数据,因为尚未提交的数据可能会被回滚,从而造成脏读。
  • 不可重复读(Non-repeatable Read):在同一个事务中,某个数据多次读取的结果不一致。这是由于其他事务在该事务读取数据期间修改了数据,导致不一致的读取结果。
  • 幻读(Phantom Read):在同一个事务中,某个范围查询多次执行的结果不一致。这是由于其他事务在该事务范围查询期间插入、删除或更新了符合查询条件的数据,导致出现新增或消失的数据行。

了解这些问题,我们再来看下事务隔离是如何解决这些问题的:

  1. 读未提交(Read Uncommitted):最低级别的隔离级别,允许事务读取尚未提交的数据。它无法解决脏读、不可重复读和幻读问题。
  2. 读已提交(Read Committed):在事务提交后才允许其他事务读取数据。它解决了脏读问题,但仍可能遇到不可重复读和幻读问题。
  3. 可重复读(Repeatable Read):事务执行期间保持一致的快照视图,其他事务无法修改已读取的数据。它解决了脏读和不可重复读问题,但仍可能遇到幻读问题。
  4. 串行化(Serializable):最高级别的隔离级别,确保事务串行执行,避免了脏读、不可重复读和幻读问题。它通过对事务加锁来实现串行化执行,但可能影响系统的并发性能。

每个隔离级别在提供数据一致性和隔离性方面都有不同的权衡和性能影响。根据应用程序的需求和对数据一致性的要求,我们可以选择合适的隔离级别来平衡并发性能和数据的正确性。

读已提交

最基本的事务隔离级别是读已提交(Read Committed),它提供了两个保证:

  1. 从数据库读时,只能看到已提交的数据(没有脏读(dirty reads))。
  2. 写入数据库时,只会覆盖已经写入的数据(没有脏写(dirty writes))。

没有脏读

在读已提交隔离级别运行的事务必须防止脏读,就是一个事物只能读取另一提交的事务。这意味着事务的任何写入操作只有在该事务提交时才能被其他人看到。

为什么要防止脏读呢?

  • 如果事务需要更新多个对象,脏读取意味着另一个事务可能会只看到一部分更新。就像我前文中举的例子,用户看到了未读数的差异
  • 如果数据库允许脏读,那就意味着一个事务可能会看到稍后需要回滚的数据,即从未实际提交给数据库的数据。 想想后果就让人头大。

没有脏写

如果先前的写入是尚未提交事务的一部分,又会发生什么情况,后面的写入会覆盖一个尚未提交的值?这被称作脏写。

举一个简单的例子,Alice和Bob两个人同时试图购买同一辆车。购买汽车需要两次数据库写入:网站上的商品列表需要更新,以反映买家的购买,销售发票需要发送给买家。在 图7-5的情况下,销售是属于Bob的(因为他成功更新了商品列表),但发票却寄送给了 Alice(因为她成功更新了发票表)。读已提交会阻止这样这样的问题。
在这里插入图片描述
如何实现读已提交呢?

最常见的情况是,数据库通过使用行锁(row-level lock)来防止脏写:当事务想要修改特定对象(行或文档)时,它必须首先获得该对象的锁。然后必须持有该锁直到事务被提交或中止。

但用锁的问题也很明显,可能会因为长事务导致其他事务等待,导致响应时间增加。

出于以上的原因,所以有了一种新的方案:对于写入的每个对象,数据库都会记住旧的已提交值,和由当前持有写入锁的事务设置的新值。 当事务正在进行时,任何其 他读取对象的事务都会拿到旧值。 只有当新值提交后,事务才会切换到读取新值。(类比于CAS无锁化)

我们如何用代码实现呢?

  1. 显式设置隔离级别:可以在数据库连接或事务开始时显式设置隔离级别为读已提交。具体的方法取决于你使用的数据库系统和编程语言。比如,在SQL Server中,可以使用下面的语句设置隔离级别为读已提交:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

在Java中,可以使用以下代码设置隔离级别为读已提交:

Connection connection = DriverManager.getConnection(url, username, password);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

快照隔离和可重复读

然而读已提交并不是完美的,我们来看另外一种场景
在这里插入图片描述
Alice在银行有1000美元的储蓄,分为两个账户,每个500美元。现在一笔事务从她的一个账户中转移了100美元到另一个账户。如果她在事务处理的同时查看其账户余额列表,不幸地在 转账事务完成前看到收款账户余额(余额为500美元),而在转账完成后看到另一个转出账户 (已经转出100美元,余额400美元)。对Alice来说,现在她的账户似乎只有900美元—— 看起来100美元已经消失了。

当然,你可能会说只需要等一小会,重新刷新一下,就会好了,但对于用户来说,这种场景还是蛮惊悚的

要解决这个问题,我们可以想到,让Alice在查询数据时,保持“数据静止”不就行了,这就是我们快照隔离的解决方案:

每个事务都从数据库的一致快照(consistent snapshot)中读取——也就是说,事务可以看到事务开始 时在数据库中提交的所有数据。即使这些数据随后被另一个事务更改,每个事务也只能看到 该特定时间点的旧数据。

那么,我们改如何去实现快照隔离呢?

从性能的角度来看,快照隔离的一个关键原则是:读不阻塞写,写不阻塞读。数据库在处理一致性快照上的长时间查询时,可以正常地同时处理写入操作。且两者间没有任何锁定争用。

我们的答案是多版本并发控制(MVCC, multi-version concurrentcy control)

我们用一个简单的比喻来描述什么是快照隔离,来更好理解MVCC:

假设你和你的朋友共享一个电子相册,其中包含了你们的旅行照片。每当你上传新的照片时,系统会为该照片创建一个时间戳,以便记录照片的版本。现在,假设你的朋友在某个时间点查看了你们的相册,并记住了这个时间点。然后你添加了一张新照片到相册中。

在MVCC机制下,当你的朋友再次查看相册时,他只能看到在他最初查看相册时已经存在的照片,而看不到你后来添加的照片。这是因为他的时间点早于新照片的时间戳,所以他只能看到旧版本的相册。

通过这个例子,我们可以更好地理解MVCC机制的工作原理。每个事务(每个人查看相册)使用自己的时间点(时间戳)来选择合适的数据版本(相册的内容),以保证数据的一致性和隔离性。这样,即使其他事务(其他人)对数据进行了修改,只要它们的时间点晚于当前事务开始的时间点,当前事务仍然可以读取到旧版本的数据,从而实现了可重复读。

持快照隔离的存储引擎通常也使用MVCC来实现读已提交隔离级别。很简单,已提交隔离级别保留一个对象的两个版本就足够了:提交的版本和被覆盖但尚未提交的版本。所以说MVCC是一种通用的实现方案
在这里插入图片描述
我们来看一下,用MVCC机制实现时,什么时候对象对于我们是可见的呢?

  • 读事务开始时,创建该对象的事务已经提交。
  • 对象未被标记为删除,或如果被标记为删除,请求删除的事务在读事务开始时尚未提交。

接下来还有一个问题:我们实现的数据的隔离,但是别忘了我们还有索引,数据的改变也会引起索引的变动,那么索引是如何在多版本数据库中工作的?

我们思考一下,有以下几种思路

1、索引简单地指向对象的所有版本,并且需要索引查询来过滤掉当前事务不可见的任何对象版本。当垃圾收集删除任何事务不再可见的旧 对象版本时,相应的索引条目也可以被删除。这种实现方式想想就工作量巨大,事务的状态变更将同时要修改索引,这些索引甚至大概率不在一个地方存储,带来的性能损耗可想而知

2、有些数据库(比如CouchDB,Datomic和LMDB),它们使用的是一种仅追加/写时拷贝(append-only/copy-on-write)的B树,它们在更新时不覆盖树的页 面,而为每个修改页面创建一份副本。从父页面直到树根都会级联更新,以指向它们子页面 的新版本。任何不受写入影响的页面都不需要被复制,并且保持不变。使用仅追加的B树,每个写入事务(或一批事务)都会创建一颗新的B树,当创建时,从该特定树根生长的树就是数据库的一个一致性快照。没必要根据事务ID过滤掉对象,因为后续写入不能修改现有的B树;它们只能创建新的树根。但这种方法也需要一个负责压缩和垃圾收集 的后台进程。

丢失更新问题

我们现在考虑这样的一种场景:

如果应用从数据库中读取一些值,修改它并写回修改的值(读取-修改-写入序列),则可能会发生丢失更新的问题。如果两个事务同时执行,则其中一个的修改可能会丢失,因为第二个 写入的内容并没有包括第一个事务的修改。

一些简单的场景就是:统计数值的并发更新、两个用户同时编辑wiki页面

上面的这种问题就是丢失更新问题

那么,针对这种问题,我们应该如何解决呢?我们肯定想到只要保证操作具有‘锁’的特性就行

1、原子写。这个要依赖数据库的支持。比如下面mysql的示例,还有MongoDB提供原子操作,redis的一些原子操作命令

UPDATE counters SET value = value + 1 WHERE key = 'foo';

2、显式锁定。就是加锁啰,FOR UPDATE加行锁

BEGIN TRANSACTION;
SELECT * FROM figures
WHERE name = 'robot' AND game_id = 222
FOR UPDATE;

UPDATE figures SET position = 'c4' WHERE id = 1234;
COMMIT;

3、CAS。加锁效率太低,CAS就能提供无锁化操作

UPDATE wiki_pages SET content = '新内容' WHERE id = 1234 AND content = '旧内容';

写入偏差与幻读

幻读是指在一个事务内,多次执行相同的查询操作,但结果却出现了新增或删除的数据行,就好像出现了幻觉一样。这是因为在这期间有其他事务插入、删除或修改了符合查询条件的数据,导致查询结果发生变化。

举一个简单的例子:

假设你去超市购买水果,你想购买的是超市里所有的苹果。你进入超市时,你看到苹果架上有10个苹果。于是你把苹果放进购物车,准备结账。但在你结账的时候,超市员工往苹果架上添加了5个苹果。当你回头看苹果架时,你发现有15个苹果。这就好像发生了幻觉,因为在你的视野中苹果的数量突然增加了。

在数据库中,幻读问题的发生方式类似。当一个事务在执行范围查询时,其他事务在这期间插入了新的符合查询条件的数据,导致了查询结果的变化,就像出现了幻觉一样。

写偏差指的是在一个事务内,多次执行相同的写操作,但结果却出现了不一致的情况。这是因为在这期间有其他事务对相同的数据进行了修改,导致写操作的结果不一致。幻读则是在一个事务内多次执行相同的范围查询操作时,结果出现了新增或删除的数据行。这是因为在这期间有其他事务对符合查询条件的数据进行了插入或删除,导致查询结果的变化。写偏差和幻读之间存在关系,即写偏差可能引发幻读问题。当一个事务在执行写操作时,如果另一个事务在同一时间对相同的数据进行了修改,那么在之后执行相同的范围查询操作时,可能会出现新增或删除的数据行,产生幻读。

快照隔离可以避免只读查询中幻读,但是在像读写事务中,幻影会导致特别棘 手的写歪斜情况。

后面的文章中,我会带你继续了解可序列化相关内容,可以关注一下哦~

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

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

相关文章

嵌入式通信协议【Modbus】Modbus TCP的帧格式

一、请求帧格式 Client request:例: 19 B2 00 00 00 06 06 03 00 27 00 02 1、头字节 上面是modbus客户端发出的报文内容,为modbus tcp/ip协议格式,其前面的六个字节为头字节( header handle); 19 B2 00 00 00 06 19 B2 00 00…

3D点云之语义分割(相关官方示例介绍)

之前在博客中提到,会考虑用深度学习来对3D点云进行处理,接下来迈出脚步,先整几个例子来熟悉它。例子原型来源于官网,博主在其基础上做了一些代码修改。 一. 例子参考 1. Keras中的资源 Code examples 2.openvinotoolkit open_…

20230515在亚马逊Amazon扣费之后的申诉

20230515在亚马逊Amazon扣费之后的申诉 2023/5/15 22:56 缘起:使用Amazon的12个月的免费存储桶,然后调用S3功能翻译! 但是,被扣费了! 由于绑定的信用卡是工行的,要求和亚马逊解除绑定,工行给了一…

第十章创建模式—外观模式

文章目录 外观模式解决的问题概念结构 案例优缺点使用场景Tomcat 源码 结构型模式描述如何将类或对象按某种布局组成更大的结构,有以下两种: 类结构型模式:采用继承机制来组织接口和类。对象结构型模式:釆用组合或聚合来组合对象。…

GEE:无监督聚类算法(wekaKMeans)

作者:CSDN @ _养乐多_ 本文将介绍如何使用 Google Earth Engine(GEE)进行卫星图像 K-means 聚类分析的基本步骤,并提供相应的示例代码。 结果如下图所示, 文章目录 一、K-means 原理二、代码详解三、代码链接一、K-means 原理 二、代码详解 var roi = table; Map.cent…

【前后端异常】axios post请求 返回415错误状态码的解决方法

错误描述: 进行有文件的表单提交时出现415错误,以前没遇到过记录一下 415错误的解释是说,服务器无法处理请求附带的媒体格式。以下是HTTP的状态码关于415返回码的说明: 415Unsupported Media Type服务器无法处理请求附带的媒体格…

awk的应用

awk的基本应用 一、awk的工作原理二、实际应用按行输出文本按字段输出文本时间的使用查看各个功能 一、awk的工作原理 awk与sed和grep共成为文本三剑客,都是针对文本非常实用的工具。 awk的工作原理: 遂行读取文本,默认以空格或者tab&#x…

NodeJs模块化之上半部分

模块化 什么是模块化 模块化是指解决一个复杂问题时,自顶向下 逐层把系统 拆解 成若干 模块 的过程。 对于整个系统来说,模块是可组合、分解和更换的单元。 编程领域中的模块化 编程领域中的模块化,就是遵守固定的规则,把一个…

( 位运算 ) 231. 2 的幂 / 342. 4的幂 ——【Leetcode每日一题】

❓题目一 231. 2 的幂 难度:简单 给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。 如果存在一个整数 x 使得 n 2 x n 2^x n2x ,则认为 n 是 2 的幂次方。 …

10款必装IDEA开发神器

那些IDEA开发神器 1.Material Theme Ul 安装步骤 打开IDEA,点击File -> Settings。在Settings窗口中,选择Appearance & Behavior -> Appearance。在Appearance选项卡下,找到Theme并选择Material Theme Ul。点击Apply按钮,然后点…

Liunx基础命令 - mv剪切命令

mv命令 – 移动或改名文件 mv命令来自英文单词”move“的缩写,中文译为”移动“,其功能与英文含义相同,能够用于对文件进行剪切和重命名操作。这是一个被高频使用的文件管理命令,我们需要留意它与复制命令的区别。cp命令是用于文…

C++标准库中的锁

C标准库中的锁 std::mutex.lock是我们在C中比较常见的锁,我们使用std::mutex.lock方法时,同时需要考虑何时使用std:mutex.unlock方法去解锁。如果在复杂的多线程情况下,加锁、解锁的时机很难把握,也不好实现。 RAII原则是所有的…

c++中this指针的使用

首先,我们都知道类的成员函数可以访问类的数据,那么成员函数如何知道哪个对象的数据成员要被操作呢? 原因在于每个对象都拥有一个指针:this指针,每一个对象都能通过this指针来访问自己的地址。 this 指针是所有成员函…

NOSQL——redis的安装,配置与简单操作

1.缓存的相关知识 1.1 缓存的概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度,在中间对速度较慢的一方起到加速作用,比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据,内存是保存CPU经常访问硬盘的数据,而且硬…

Git简易教程

安装与配置 教程链接 代码上传到远程仓库 workspace:当前所在的工作目录 stagingarea:修改暂存区 loacl repository:本地仓库 remote respository :远程仓库 我们首先在workspace目录编写代码、修改文件,然后需要使用…

三十五、nacos注册中心集群搭建、网关、过滤器

1、nacos注册中心集群的搭建 要想搭建nacos集群模式,这些集群中的每台nacos服务,都必须连接同一个数据库。因为我们的nacos都放在同一台主机上,所以我们必须为每台nacos起不同的端口号。 1.1修改nacos端口号 8849 8850 8851 E:\software\naco…

2023/5/15总结

Java基础(4) 标准类制作 成员变量使用private修饰构造方法:提供一个无参构造方法,提供一个带多个参数的构造方法 成员方法:提供每一个成员变量对应的setXxx()/getXxx(),提供一个显示对象信息show()创建对象并为其成员…

服务端渲染

服务端渲染 和 前后端分离&#xff01; 渲染 什么是渲染呢 ? 其实很简单, 就是把数据反应在页面上&#xff0c;说白了, 就是利用 js 的语法, 把某些数据组装成 html 结构的样子, 放在页面上展示。 举个例子 : 1. 准备一段 html 结构 <h1>hello world</h1> <di…

Android之 Service服务详解

一 四大组件 1.1 Activity组件&#xff0c;它一个单独的窗口&#xff0c;程序流程都必须在Activity中运行&#xff0c;所有它是最基本的模块。 1.2 service组件&#xff0c;用于在后台完成用户指定的操作。 1.3 content provider组件&#xff0c;会为所有的应用准备一个内容…

零知识证明:重要构造

文章目录 ZKP for NPBlums ZK PoK for HCCompletenessSoundnessZero KnowledgeWI of n-parallelized versionsProof of KnowledgeSpecial Soundness Constant Round ZKPFLS ParadigmGK Paradigm 在 零知识证明&#xff1a;安全定义 中介绍了 ZKP 的一些安全性定义&#xff0c;…