事务的ACID

 

事务概述

我们可以把事务理解成一组sql语句的集合,这样描述可能不太容易理解,不要着急,我们先大概的描述一下理论,然后再进行形象的举例。

 

事务可以只包含一条sql语句,也可以包含多条复杂的sql语句,事务中的所有sql语句被当做一个操作单元,换句话说,事务中的sql语句要么都执行成功,要么全部执行失败,事务内的sql语句被当做一个整体,被当做一个原子进行操作。

 

mysql中,innodb存储引擎是支持事务的,而且innodb存储引擎的事务完全符合ACID的特性,ACID是如下四大特性的首字母缩写。

A:atomicity    原子性

C:consistency   一致性

I:isolation    隔离性

D:durability   持久性

 

原子性:整个事务中的所有操作要么全部执行成功,要么全部执行失败后混滚到最初状态。

一致性:数据库总是从一个一致性状态转为另一个一致性状态。

隔离性:一个事务在提交之前所做出的的操作是否能为其他事务可见,由于不同的场景需求不同,所以针对隔离性来说,有不同的隔离级别。

持久性:事务一旦提交,事务所做出的修改将会永久保存,此时即使数据库崩溃,修改的数据也不会丢失。

 

上述特性是ACID的理论基础,而innodb存储引擎的事务又符合ACID的特性,那么,我们来举个栗子,以方便我们理解。

首先我们来描述一个场景,在学习事务时一般都会使用这个场景作为举例,这个场景就是"转账"。

 

"天朝银行"有很多用户,目前,A用户账户上的余额为8000元,B用户账上的余额为5000元,现在A用户要向B用户转账1000元。

那么,当转账结束以后,A用户账户上的余额应该为7000元,B账户上的余额应该为6000元。

那么上述过程在数据库中应该转换为如下操作。

 

操作1:修改A用户账户对应的余额记录,8000-1000

操作2:修改B用户账户对应的余额记录,5000+1000

 

上述操作好像没毛病,但是假设,如果数据库刚刚完成操作1,好巧不巧,这个时候停电了,过了两分钟,又来电了,当我们再次查看数据库时,发现A用户余额为7000,比停电之前少了1000,发现B用户的账户余额仍然为5000,与停电之前一样,出现这种情况是因为数据库只完成了操作1,而没来得及完成操作2,那么,1000块大洋凭空消失了,所以,我们应该防止这样的悲剧发生,没错,解决方法就是使用事务。

 

我们之前说过,事务中的所有sql语句都被当做一个整体,要么全部执行成功,要么在其中某些操作执行失败后回滚,回滚到最初的状态,就好像什么都没有发生过一样。那么利用事务的这个特性,就可以解决之前的问题,我们可以把转账的sql语句写入到事务中,如下。

 

事务开始

update A用户余额 - 1000

update B用户余额 + 1000

提交事务(事务结束)

 

利用事务完成上述操作,即使数据库刚刚将A用户账户余额减去1000时停电了,由于事务的特性,当再次使用数据库时,也不会出现A用户余额变为7000,B用户余额仍然为5000的情况,为什么呢?事务是怎样实现这样的功能的呢?你一定想到了这些问题,答案就是,事务是通过"事务日志"来实现这种功能的。

 

"事务日志"可以细分为 redo log 和 undo log,我们一个一个聊。

 

redo log概述

先说redo log,mysql会将事务中的sql语句涉及到的所有数据操作先记录到redo log中,然后再将操作从redo log中同步到对应数据文件中(此处假设事物操作的数据量并非巨大),换句话说,在事务执行提交成功以前,在修改对应的数据文件中的记录之前,一定要保证对应的所有修改操作已经记录到了redo log中,假设事务中的sql语句涉及到60条记录的修改,那么在修改这60条记录之前,要将这60条修改操作记录到redo log中,当这60条操作都记录到redolog中以后,再从redo log中一条一条同步到数据文件的对应记录中。所以,即使数据文件中的数据被修改到一半时被打断(比如停电),那么也能依靠redo log中的日志将剩余的部分操作再次同步到对应的数据文件中。

 

使用redo log,能够实现ACID中的A,也就是原子性,即事务中的所有sql被当做一个执行单元。

redo log其实由两部分组成:redo log buffer(重做日志缓冲) 和 redo log file(重做日志文件)

redo log buffer存在于内存之中,是易失的,redo log file是持久的,存在于磁盘上。

mysql/mariadb知识点总结(19):事务相关概念 (事务总结之一)

重做日志先被写入到redo log buffer中,虽然内存的速度极快,但是无法满足持久性的需求,因为内存中的数据是易失的,所以为了满足持久性,需要将redo log buffer中的日志写入到redo log file中,相当于从内存中同步到磁盘上,所以磁盘的性能会影响事务的性能,由于redo log file是磁盘上一段连续的空间,所以写速度还是比较快的,比离散的写操作要快很多,当操作记录被记录到redo log file中以后,再从redo log file中将操作同步到数据文件中。

虽然,我们应该实时将redo log buffer中的数据写入到redo log file中以保证数据的安全性,但是这样会极大的降低性能,我们可以通过设置innodb_flush_log_at_trx_commit参数来修改从redo log buffer写入redo log file的策略,但是如果这样做,则会丧失持久性,有可能会丢失部分数据,具体使用怎样的刷写策略,还需要根据实际情况自己权衡。

 

redo log是物理日志,之所以说它是物理日志,是因为redo log 中记录的是数据库对页的操作,而不是逻辑上的增删改查,重做日志具有幂等性。

 

redo log先说到这里,一会儿再回过头来聊redo log。

 

undo log概述

刚才我们大致的描述了什么是redo log ,现在来聊聊什么是undo log,我们可以把undo log理解成数据被修改前的备份。如果说事务进行了一半,有一条sql没有执行成功,那么数据库可以根据undo log进行撤销,将所有修改过的数据从逻辑上恢复到修改之前的样子,注意,是逻辑上还原成原来的样子,比如,之前insert了1000条数据 ,那么就delete它们,如果delete了2000条,就insert它们,如果update了500条数据,就再次根据undo log去update它们,所以,undo log是逻辑日志,与redo log记录的页操作物理日志不同。

 

log group概述

log group为重做日志组,一个重做日志组(log group)中有多个重做日志文件(redo log file),当日志组中的第一个logfile被写满,则会开始将redo log写入日志组中的下一个重做日志文件中,以此类推,当日志组中的所有redo log file都被写满,则将redo log再写入第一个redo log file 中,覆盖原来的redo log,以便新的redo log 被写入。

如果重做日志所在的设备崩溃了,那么redo log将有可能丢失,这样就无法保证redo log在任何时候都是可用的,所以,log group还支持日志组镜像,为了保险起见,我们应该将log group放在有冗余能力的设备上,比如raid1。

 

其他

redo log存储于重做日志文件中,undo log则不同,undo存放在数据库内部的特哥特殊段中,这个段被称为undo段(undo segment),undo段位于共享表空间中。

 

mysql中,innodb存储引擎是支持事务的,myisam存储引擎是不支持事务的,不管是redo log或者undo log,都是innodb的产物,或者说是innodb存储引擎层面的产物,而在mysql中,还有另外一种重要的日志,二进制日志,也就是平常所说的binlog,它是建立mysql主从复制环境时所必须的日志,但是binlog并不是innodb存储引擎层面的产物,而是整个mysql数据库层面的产物,换句话说,binlog不止针对于innodb,mysql数据库中的任何存储引擎对于数据库的更改都会产生二进制日志(binlog)。

innodb的redo log记录的是物理格式的日志,记录了对页的操作,而binlog记录的是逻辑日志,记录的是对应的SQL。

redolog与binlog写入磁盘的时机也不同,innodb的redo log在事务进行时会不断的写入redo log file,binlog只在事务提交完成后进行一次磁盘写入。

 

其实,不管是redo log 还是 undo log,都可以理解成恢复数据库的手段。

posted @ 2019-11-26 20:14  windpoplar  阅读(161)  评论(0编辑  收藏  举报