数据库详解之事务

数据库详解之事务,第1张

究竟什么是数据库的事务,为什么数据库需要支持事务,为了实现数据库事务各种数据库的是如何设计的。还是只谈理解,欢迎大家来讨论。

1. 数据库事务是什么

事务的定义,已经有太多文章写过,我就不重复了。我理解的事务就是用来保证数据 *** 作符合业务逻辑要求而实现的一系列功能。换句话说,如果数据库不支持事务,上面业务系统的程序员就需要自己写代码保证相关数据处理逻辑的正确性。而数据库事务就是把一系列保证数据库处理逻辑正确性的通用功能在数据库内实现,并且尽量提高效率。

举个例子,数据库最开始普及就是在金融业,银行的存取款场景就是一个最典型的OLTP数据库场景,而事务就是设计用来保证类似场景的业务逻辑正确性的。

![事务的四个基本特性](https://img-blog.csdnimg.cn/2967b3d9484c405289d711ef5bf69890.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAd2luZHRhbGtlcnd5,size_20,color_FFFFFF,t_70,g_se,x_16)

**原子性**,如果你要给家人转账,必须在你的账户里扣掉100块,在家人账户里加上100块,这两笔 *** 作需要一起完成,业务逻辑才是正确的。但是程序在做修改的时,肯定会有先后顺序,试想一下程序扣了你的钱,这个时候程序崩溃了,家人账户的钱没有加上。那这100块是不是消失了?你是不是要发疯?那么,就把这两笔 *** 作放进一个事务里,通过原子性保证,这两笔 *** 作要么都成功,要么都失败。这样才能保证业务逻辑的正确性。

**一致性**,有很多文章讲过一致性,但是很多人会把一致性跟原子性混在一起说。事务的一致性指的是指每一个事务必须保证执行之后所有库内的规则依旧成立。比如内外键,constraint,触发器等。举例来说,你在储蓄卡里有100元,理财账户里有100元,基金账户有100元,那么你在资产总和里会看到300元,这个300元必须是其他三个账户余额加在一起得到的。你在给家人转帐100元是从储蓄卡里转出去了100元,那么在数据库上可以通过创建触发器的方式,当储蓄卡余额账户减100元的同时,把资产总和也同步减去100,不然的话,就会出现逻辑上的错误,因为你已经转走了100块储蓄卡余额,实际资产总和应该是200,如果还是300,数据库状态就不一致了。所以实现事务的时候,必须要保证相关联的触发器以及其他所有的内部规则都执行成功,事务才能算执行成功。如果在减去资产总时出错,那么这笔转帐交易也不能成功。因为这样数据库就会进入不一致的状态。

那么这里跟原子性的区别到底在哪里呢?原子性是指个多个用户指令之间必须作为一个整体完成或失败,而一致性更多是数据库内的相关数据规则必须同时完成或失败。

**持久性**,最容易理解的一个,事务只要提交了,那么对数据库的修改就会保存下来不会丢了。简单来说,只要提交了,数据库就算崩溃了,重启之后你刚存的100块依然在你的账户里。

**隔离性**,每个事务相对于其他的事务是有一定独立性的,不能互相影响。因为数据库需要支持并发的 *** 作来提高效率。在并发 *** 作时,一定要通过 *** 作之间的隔离来保证业务逻辑的正确性。比如,你转帐100块给家人,一系列 *** 作的最后一步可能是输入验证码,这个时候转帐还没有完成,但是在数据库里你的账户对应的记录中已经减去100块,家人账户也加了100块,就等着验证码输入以后,事务提交,完成 *** 作。那么,这个时候,家人通过手机银行能够查到这100块么?你的答案可能是不能,因为这样才符合业务逻辑,因为你的转帐 *** 作还没有提交,事务还没有完成。那么数据库就应该保证这两个并发 *** 作之间具有一定的隔离性。

那么到底应该隔离到什么程度呢?隔离性又分为4个等级:由低到高依次为Read uncommitted(读未提交)、Read committed(读提交)、Repeatable read(可重复读取)、Serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。这些东西是什么意思?请有兴趣的小伙伴自行百度,很多文章都写的很清楚。

那么怎么理解不同的隔离等级呢,首先要理解并发 *** 作,并发 *** 作就是指有不同的用户同时对一个数据进行读、写 *** 作,那么在这个过程中,每个用户应该看到什么数据才能保证业务逻辑的正确性呢? 如果是前面存取款的场景,我必须看到的是已经存进来的钱,也就是必须是已经提交的事务。而12306刷火车票呢,你可以看到有10张余票,但是在下单的时候告诉你票卖完了,因为同时有10个用户把票买掉了,你需要重新刷余票,这个也是可以接受的,也就是说我可以读到一些虚假的余票,这样在业务上也没有什么问题。那么在设计这两个不同系统时,就可以选择不同的事务隔离级别来实现不同的并发效果。不同的隔离等级就是要在系统的并发性和数据逻辑的严谨性之间做出的平衡。

2. 数据库如何实现事务

数据库实现事务会有多种不同的方式,但基本的原理类似,比如都需要对事务进行统一的编号处理,都需要记录事务的状态(是成功了还是失败了),都需要在数据存储的层面对事务进行支持,以明确哪些数据是被哪些事务、插入、修改和删除的。同时还会记录事务日志等,对事务进行系统化的管理以实现数据的原子性,一致性和持久性。

要实现事务的隔离性,最基础的就是通过加锁机制把并发 *** 作适当的串行化来保证数据 *** 作的正确逻辑。但是为了要保证系统具有良好的并发性能,必须要在实现事务隔离性时需要找到合理的平衡点。大部分数据库(包括Oracle,MySQL,Postgres在内)在做并发控制的时候都会采用MVCC(多版本并发控制)的机制来保证系统具有较高的并发性,不同数据库实现MVCC的具体方案也不尽相同,但其基本原理类似。

3. MVCC实现原理

所谓MVCC,就是数据库中的同一查询根据相关事务执行的先后顺序以及隔离级别的不同,可能会存在不同版本的结果,通过这样的手段来保证大部分查询 *** 作不会被修改 *** 作阻塞并保证数据逻辑的正确性。也就是数据库通过保存多个版本的数据( 历史 数据)来提高系统的并发查询能力。简单来说就是用存储空间来交换并发能力。下面以Postgres为例介绍一下MVCC的一种实现方式帮助大家理解这个重要的数据库概念。通过下面的图来解释Posrgres里最基本的数据可见性是如何实现多版本控制的。

![在这里插入图片描述](https://img-blog.csdnimg.cn/fd880f62b48d4a43986fa9c7d1a79e4e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAd2luZHRhbGtlcnd5,size_20,color_FFFFFF,t_70,g_se,x_16)

首先,Postgres里的每一个事务都有编号,这里可以简单理解为时间顺序编号,编号越大的事务发生越晚。然后,数据库里的每一行记录都会保存创建这条记录的事务号(Cre),也会在记录删除时保存删除这条记录的事务号(Exp),换句话说,只要Exp这里一列里记录了事务编号,就说明这条记录被删除了。那么一个事务应该能看见那些记录呢?Postgres里每一个事务都会保存一个当前系统的事务快照(Snapshot),这个快照里会保存事务创建时当前系统的最高(最晚)事务编号,以及目前还在进行中的事务编号。那么如上图所示的一个事务的快照里最高事务编号为100,目前正在进行的事务有25,50和75。那么对应左边数据记录,这6行数据的可见性就如同标注的一般:

第一行,Cre 30,没有删除,在100这个时间点,应该能看到。

第二行,Cre 50,没有删除,但是50这个事务还没有提交,正在进行中,所以看不见。

第三行,Cre 110,没有删除,但是100这个时间点110事务还没有发生,所以看不见。

第四行,Cre 30,Exp 80,在80的时候数据被删掉了,所以看不见。

第五行,Cre 30,Exp 75,在30的时候被创建,75时候被删掉了,但是75这个事务在100的时候还没有提交,所以这条记录在100的时候还没有删掉,所以看得见。

第六行,Cre30,Exp 110,在30的时被创建,110时候被删掉,但是在100时候,110还没有发生,所以看得见。

综上,就是这个事务对这六条记录的可见性,也就是一个数据版本。那么大家可以看一下如果另一个事务的快照里存的是最高事务编号为110,正在进行的事务为50,那么它能看到的数据应该是哪几行呢?同时大家也看到,Postgres里删除一行数据其实就是在这一行的Exp这个列记录一个删除事务的编号,相当于做了一个删除标记,而数据没有真正被删除,因此Postgres数据库需要定期做数据清理 *** 作(Vacuum)。Pstgres的在现实场景里会比这里介绍的要复杂,因为我们这里假定所有的事务最终都是正确提交了,如果存在某些事务没有提交的情况,那么可见性就会更加复杂,这里不再展开了。

数据库事务是基本的数据库概念,之前已经有很多很好文章做过介绍,这里希望能把自己的理解用比较通俗的描述分享给大家,欢迎来讨论交流。

ACID 是为保证事务(transaction)是正确可靠的,所必须具备的四个特性:

以 A 给 B 转账100元为例:

MySQL事务是由 InnoDB 存储引擎实现的。

可以用如下的命令显式的开启事务:

另外,在自动提交(autocommit)模式下,我们执行的每一条 SQL 语句都是一条独立的事务;如果关闭了自动提交(autocommit)模式,则所有的 SQL 语句都在一个事务中,直到执行了 commit 或 rollback,该事务结束,同时开始了另外一个事务。

MySQL 事务的 ACID 特性靠如下机制实现:

Go 语言的 Gorm 提供了对于事务 *** 作的支持:

此外,还有嵌套事务以及手动事务等 *** 作,可以参考中文文档: learnku.com/docs/gorm/v…

@Transactional 注解必须添加在public方法上,private、protected方法上是无效的。

一般情况下,推荐将@Transactional 注解加在方法上,因为@Transactional直接加在类或者接口上,@Transactional注解会对类或者接口里面所有的public方法都有效,会影响性能。

回答的有点多请耐心看完。

希望能帮助你还请及时采纳谢谢

1事务的原理

事务就是将一组SQL语句放在同一批次内去执行,如果一个SQL语句出错,则该批次内的所有SQL都将被取消执行。MySQL事务处理只支持InnoDB和BDB数据表类型。

1事务的ACID原则

** 1(Atomicity)原子性**: 事务是最小的执行单位,不允许分割。原子性确保动作要么全部完成,要么完全不起作用;

2(Consistency)一致性: 执行事务前后,数据保持一致;

3(Isolation)隔离性: 并发访问数据库时,一个事务不被其他事务所干扰。

4(Durability)持久性: 一个事务被提交之后。对数据库中数据的改变是持久的,即使数据库发生故障。

1缓冲池(Buffer Pool)

Buffer Pool中包含了磁盘中部分数据页的映射。当从数据库读取数据时,会先从Buffer Pool中读取数据,如果Buffer Pool中没有,则从磁盘读取后放入到Buffer Pool中。当向数据库写入数据时,会先写入到Buffer Pool中,Buffer Pool中更新的数据会定期刷新到磁盘中(此过程称为刷脏)。

2日志缓冲区(Log Buffer)

当在MySQL中对InnoDB表进行更改时,这些更改命令首先存储在InnoDB日志缓冲区(Log Buffer)的内存中,然后写入通常称为重做日志(redo logs)的InnoDB日志文件中。

3双写机制缓存(DoubleWrite Buffer)

Doublewrite Buffer是共享表空间的物理文件的 buffer,其大小是2MB.是一个一分为二的2MB空间。

刷脏 *** 作开始之时,先进行脏页**‘备份’** *** 作.将脏页数据写入 Doublewrite Buffer.

将Doublewrite Buffer(顺序IO)写入磁盘文件中(共享表空间) 进行刷脏 *** 作.

4回滚日志(Undo Log)

Undo Log记录的是逻辑日志.记录的是事务过程中每条数据的变化版本和情况.

在Innodb 磁盘架构中Undo Log 默认是共享表空间的物理文件的Buffer.

在事务异常中断,或者主动(Rollback)回滚的过程中 ,Innodb基于 Undo Log进行数据撤销回滚,保证数据回归至事务开始状态.

5重做日志(Redo Log)

Redo Log通常指的是物理日志,记录的是数据页的物理修改.并不记录行记录情况。(也就是只记录要做哪些修改,并不记录修改的完成情况) 当数据库宕机重启的时候,会将重做日志中的内容恢复到数据库中。

1原子性

Innodb事务的原子性保证,包含事务的提交机制和事务的回滚机制.在Innodb引擎中事务的回滚机制是依托 回滚日志(Undo Log) 进行回滚数据,保证数据回归至事务开始状态.

2那么不同的隔离级别,隔离性是如何实现的,为什么不同事物间能够互不干扰? 答案是 锁 和 MVCC。

3持久性

基于事务的提交机制流程有可能出现三种场景.

1 数据刷脏正常.一切正常提交,Redo Log 循环记录.数据成功落盘.持久性得以保证

2数据刷脏的过程中出现的系统意外导致页断裂现象 (部分刷脏成功),针对页断裂情况,采用Double write机制进行保证页断裂数据的恢复.

3数据未出现页断裂现象,也没有刷脏成功,MySQL通过Redo Log 进行数据的持久化即可

4一致性

从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性

2事务的隔离级别

Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别

脏读: 指一个事务读取了另外一个事务未提交的数据。

不可重复读: 在一个事务内读取表中的某一行数据,多次读取结果不同

虚读(幻读): 是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

2基本语法

-- 使用set语句来改变自动提交模式

SET autocommit = 0 /*关闭*/

SET autocommit = 1 /*开启*/

-- 注意:

--- 1.MySQL中默认是自动提交

--- 2.使用事务时应先关闭自动提交

-- 开始一个事务,标记事务的起始点

START TRANSACTION

-- 提交一个事务给数据库

COMMIT

-- 将事务回滚,数据回到本次事务的初始状态

ROLLBACK

-- 还原MySQL数据库的自动提交

SET autocommit =1

-- 保存点

SAVEPOINT 保存点名称 -- 设置一个事务保存点

ROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点

RELEASE SAVEPOINT 保存点名称 -- 删除保存点

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

/*

课堂测试题目

A在线买一款价格为500元商品,网上银行转账.

A的yhk余额为2000,然后给商家B支付500.

商家B一开始的yhk余额为10000

创建数据库shop和创建表account并插入2条数据

*/

CREATE DATABASE `shop`CHARACTER SET utf8 COLLATE utf8_general_ci

USE `shop`

CREATE TABLE `account` (

`id` INT(11) NOT NULL AUTO_INCREMENT,

`name` VARCHAR(32) NOT NULL,

`cash` DECIMAL(9,2) NOT NULL,

PRIMARY KEY (`id`)

) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO account (`name`,`cash`)

VALUES('A',2000.00),('B',10000.00)

-- 转账实现

SET autocommit = 0-- 关闭自动提交

START TRANSACTION -- 开始一个事务,标记事务的起始点

UPDATE account SET cash=cash-500 WHERE `name`='A'

UPDATE account SET cash=cash+500 WHERE `name`='B'

COMMIT-- 提交事务

# rollback

SET autocommit = 1-- 恢复自动提交

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

3事务实现方式-MVCC

1什么是MVCC

MVCC是mysql的的多版本并发控制即multi-Version Concurrency Controller,mysql的innodb引擎支持MVVC。MVCC是为了实现事务的隔离性,通过版本号,避免同一数据在不同事务间的竞争,你可以把它当成基于多版本号的一种乐观锁。当然,这种乐观锁只在事务级别为RR(可重复读)和RC(读提交)生效。MVCC最大的好处,相信也是耳熟能详:读不加锁,读写不冲突,极大的增加了系统的并发性能。

2MVCC的实现机制

InnoDB在每行数据都增加两个隐藏字段,一个记录创建的版本号,一个记录删除的版本号。

在多版本并发控制中,为了保证数据 *** 作在多线程过程中,保证事务隔离的机制,降低锁竞争的压力,保证较高的并发量。在每开启一个事务时,会生成一个事务的版本号,被 *** 作的数据会生成一条新的数据行(临时),但是在提交前对其他事务是不可见的;对于数据的更新(包括增删改) *** 作成功,会将这个版本号更新到数据的行中;事务提交成功,新的版本号也就更新到了此数据行中。这样保证了每个事务 *** 作的数据,都是互不影响的,也不存在锁的问题。

3MVCC下的CRUD

SELECT:

当隔离级别是REPEATABLE READ时select *** 作,InnoDB每行数据来保证它符合两个条件:

** 1 事务的版本号 大于等于 创建行版本号**

  ** 2 行数据的删除版本 未定义 或者大于 事务版本号**

  【行创建版本号 事务版本号 行删除版本号】

 

INSERT:

InnoDB为这个新行 记录 当前的系统版本号。

DELETE:

InnoDB将当前的系统版本号 设置为 这一行的删除版本号。

UPDATE:

InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为 当前的系统版本号。它同时也会将这个版本号 写到 旧行的删除版本里。

————————————————

版权声明:本文为CSDN博主「@Autowire」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zs18753479279/article/details/113933252


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/9917313.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-03
下一篇 2023-05-03

发表评论

登录后才能评论

评论列表(0条)

保存