带你理解事务(上)、基本概念

简介: 带你理解事务(上)、基本概念

简介和特性


对于程序猿来说,编程就是将大千世界的各种业务用代码表示出来,所以代码输出的最终结果也要和现实世界能够对应的上。我们用银行转账来举例子,比如我给我的朋友A转100元,最终的结果无非就是有两种,一是成功,我的账户余额减少100,我朋友的账户余额增加100,二是失败,我们两账户余额都没有变。肯定是不允许出现我的账户余额减少了但是我朋友的账户余额没有增加,或者是我朋友的账户余额增加了我的账户余额却没有减少,出现这两种现象都会让我们和银行的关系变得极不和谐。

例子中转账这件事情虽然看起来很简单,但是当变成代码的时候就比较麻烦了,我们来看看变成代码之后会是什么样子:


1686815526835.png


乍看之下感觉很简单好像没有什么问题,但是如果在2和3中间增加了一步检查朋友账户是否为合法账户的步骤变成如下:


1686815533024.png


我们假定,第3步这步出了问题,我朋友的账户是个洗钱账户,这时候转账流程要结束,但是有个问题是我的账户已经扣掉了100元,如果不还我100的话那我不亏死,这样估计银行招牌早就被砸了。所以这个转账流程必须有如下几个特点:


1、原子性(Atomic)

要么转账成功,要不就没转,不能存在转了一半的这种情况。换句话说,转账这个过程的所有环节是不可分割的,不管转账的过程如何复杂,最后的结果是要么成功,我的账户少了钱,我朋友账户钱增加了对应数量的钱,要么是我的账户和我朋友的账户的钱都没有改变。这个特点我们称它为原子性。


2、一致性(Consistency)

这一点网上的很多博客解释的都不正确。这个特点其实指的是,转账这个过程中的数据是要能和现实世界的规则对应起来,或者说是需要满足一定的规则约束,比如,我的账户钱必须大于0,并且扣完钱也是必须大于0的。不满足这样的约束的话则说明数据是有问题的


3、隔离性(Isolation)

现实世界中的同一时刻,是有多个转账同时发生的,这同时发生的多个转账之间的数据不能相互影响,要把这个问题说清楚会稍微复杂一些,我们慢慢来说,我们把我们之前提的转账流程拆的更细一点如下:


  • (1)读取我账户上的金额,假如此时金额101
  • (2)将余额扣除100
  • (3)把剩余的余额写到磁盘上
  • (4)读取朋友账户金额
  • (5)给朋友账户金额增加100
  • (6)将新的余额写到磁盘上 假设现在有两个这样的转账过程同时发生,在计算机中,由于多处理器的原因,这两个转账并不会严格的按照彻底做完一个

时间线 转账1 转账2
1 读取我账户上的金额,此时金额101
2 读取我账户上的金额,此时金额101
3 将余额扣除100
4 把剩余的余额写到磁盘上
5 将余额扣除1
6 把剩余的余额写到磁盘上
7 读取朋友账户金额
8 读取朋友账户金额
9 给朋友账户金额增加100
10 将新的余额写到磁盘上
11 给朋友账户金额增加1
12 将新的余额写到磁盘上


相信不少人已经看出问题了吧,由于在时间线2处读到的钱还是101,因此在第6处写到磁盘上的我的余额是100,这时候银行不得亏死,因此,正确的流程是时间线134这三个事情必须先做,然后时间线2处的才允许发生,正确流程如下:


时间线 转账1 转账2
1 读取我账户上的金额,假如此时金额101
2 将余额扣除100
3 把剩余的余额写到磁盘上
4 读取我账户上的金额,此时金额是1
5 将余额扣除1
6 把剩余的余额写到磁盘上
7 读取朋友账户金额
8 读取朋友账户金额
9 给朋友账户金额增加100
10 将新的余额写到磁盘上
11 给朋友账户金额增加1
12 将新的余额写到磁盘上


4、持久性(Durability)


一旦转账完成之后,数据就永久的被持久化到磁盘上不会丢失。

我们所说的事务(transcation)必须具有这四种特性,否则就会出现问题。


事务并发读写会遇到的问题


我们用修改数据库中我的账户的余额为例子,来看看多个事务同时发生都有可能出现什么问题:


脏写


时间线 事务1 事务2
1 start
2 start
3 update user set money=100 where id=1
4 update user set money=50 where id=1
5 commit
6 select money from user where id=1 (此时查出来money是50)
7 commit


这个案例中,事务2修改了事务1还没有提交的数据,事务1在时间线6的重新查询的时候一脸懵逼,明明自己的钱应该是100的,却不知道为什么变成了50,就这样白白的损失了50块钱,和银行的关系变得极其不和谐。


脏读


时间线 事务1 事务2
1 start
2 start
3 update user set money=100 where id=1
4 select money from user where id=1 (查出来的money是100)
5 update user set money=50 where id=1
6 commit
7 commit


这个案例中,事务1先把数据库中我的账户余额更新为了100,然后事务2来查询发现余额是100,就告诉我余额是100,这种读到了别的事务未提交的数据就叫做脏读。有人不理解这个“脏”字体现在了哪里,因为事务1还是有可能继续修改money字段的,比如在上面的时间线6的时候事务1修改了money,这样的话事务2读到的数据其实是个错误的数据。


不可重复读


时间线 事务1 事务2
1 start
2 start
3 select money from user where id=1 (此时查出来的money是100)
4 update user set money=50 where id=1
5 select money from user where id=1(此时查出来的money是50)
6 commit
7 commit


事务1在时间线5的时候读到了事务2还没有提交的数据,并且和自己在时间线3读到的数据不一样,这时候我们称这种现象称为不可重复读。


幻读


时间线 事务1 事务2
1 start
2 start
3 select * from user where money>100 (假设查到了一条数据)
4 insert into user (money,id) values (200,2)
5 select * from user where money>100 (此时会查到两条数据)
6 commit
7 commit


事务1在时间线3和时间线5查到的数据不一样,并且时间线5读到了时间线3没有读到的数据,我们称这种现象为幻读。

四种现象的严重程度排序 脏写 > 脏读 > 不可重复读 > 幻读


隔离级别


注意,这里是以MySQL为例子,说的是MySQL中使用Innodb时候的事务隔离级别,不同的数据库对隔离级别的支持是不一样的。在MySQL中,支持下面四种事务隔离级别:

  • READ UNCOMMITTED: 读未提交
  • READ COMMITTED: 读已提交
  • REPEATABLE READ: 可重复读
  • SERIALIZABLE: 串行化

事实上这是sql标准中的四种隔离级别,sql标准中还规定了不同隔离级别中可以发生什么问题和不可以发生什么问题,具体如下:


隔离级别 脏写 脏读 不可重复读 幻读
READ UNCOMMITTED 可能出现 可能出现 可能出现
READ COMMITTED 可能出现 可能出现
REPEATABLE READ 可能出现
SERIALIZABLE


我们可以看到,由于脏写太过于严重,所以在哪个事务隔离级别下都不允许发生。

目录
相关文章
|
负载均衡 Linux 数据库
阿里云轻量应用服务器套餐收费标准参考(组合套餐、负载均衡套餐等)
阿里云轻量应用服务器有多种套餐,在购买轻量应用服务器、轻量应用负载均衡、轻量容器服务和轻量数据库服务时,我们可以根据业务需求选择合适的套餐。本文为大家介绍阿里云轻量应用服务器套餐和镜像最新价格表以及相关收费说明。
1023 0
阿里云轻量应用服务器套餐收费标准参考(组合套餐、负载均衡套餐等)
|
虚拟化
VMware Tools 失效处理
VMware Tools 失效处理
402 0
|
存储 缓存 弹性计算
阿里巴巴开源 容器镜像加速技术DADI 上手指南
阿里资深技术专家在阿里云开发者社区特别栏目《周二开源日》直播中,介绍刚于3月份开源的容器镜像加速器项目 DADI ,并带大家快速上手使用。本文为直播内容文字整理,看直播回放,请点击文首链接~
阿里巴巴开源 容器镜像加速技术DADI 上手指南
|
12月前
|
缓存 负载均衡 算法
深入探索Linux内核的调度机制
本文旨在揭示Linux操作系统核心的心脏——进程调度机制。我们将从Linux内核的架构出发,深入剖析其调度策略、算法以及它们如何共同作用于系统性能优化和资源管理。不同于常规摘要提供文章概览的方式,本摘要将直接带领读者进入Linux调度机制的世界,通过对其工作原理的解析,展现这一复杂系统的精妙设计与实现。
585 8
|
Kubernetes 安全 微服务
使用 Istio 缓解电信 5G IoT 微服务 Pod 架构的安全挑战
使用 Istio 缓解电信 5G IoT 微服务 Pod 架构的安全挑战
234 8
|
数据采集 前端开发 JavaScript
动态与静态网站抓取的区别:从抓取策略到性能优化
本文详细介绍了动态与静态网站抓取的区别、抓取策略及性能优化技巧,并提供了相关代码示例。静态网站抓取通过简单的HTTP请求和解析库实现,而动态网站则需使用Selenium等工具模拟浏览器执行JavaScript。文章还展示了如何使用代理IP、多线程和合理的请求头设置来提高抓取效率。
493 2
动态与静态网站抓取的区别:从抓取策略到性能优化
|
SQL 存储 分布式计算
spark执行sql的原理是什么
spark执行sql的原理是什么
331 1
|
存储 编译器 程序员
int 和 long 的区别
int 和 long 的区别
|
机器学习/深度学习 人工智能 分布式计算
主从模式(Master-Slave Architecture)
主从模式(Master-Slave Architecture)
911 0
|
JavaScript
layui laydate日期初始化的一些坑
【2月更文挑战第8天】layui laydate日期初始化的一些坑