事务的四个特性: ACID
- Atomicity: 原子性,一个事务包含多个操作,要么完全执行所有部分,要么不执行任何操作。如果执行过程发生意外错误,应该支持回滚操作。
- Consistency: 一致性,数据库操作前后应该保持某个特征不变
- Isolation: 隔离性,事务提交之前,相关数据不能被其他事务访问
- Durability: 持久性,事务提交后,修改将被永久保存于数据库中
事务的隔离级别:
- 脏读: 实时读取其他事务未提交的数据,数据随时可能会发生修改
- 不可重复读: 读取(上一次)提交后的数据,不可读其他事务正在修改的数据,但是对方修改提交后,该事务再次读取时结果与最初不一致
- 幻读: 可重读,读取实时数据,但是读取后只要自己未修改数据,下次读取时仍然与首次读取的数据一致,引起读取数据与实际数据不符
- 加锁读: 串行化,当事务对某个数据访问、操作时,其他事务对该数据的访问与操作会被阻塞,直到事务提交后放行操作
| 隔离级别 | 存在的问题 |
|---|---|
| READ-UNCOMMITTED | 脏读、不可重复读、幻读 |
| READ-COMMITTED | 不可重复读、幻读 |
| REPEATABLE-READ | 幻读 |
| SERIALIZABLE | 加锁读 |
mysql> SET transaction_isolation='isolation_level'; // 仅对当前会话有效
事务操作主要为两个步骤:启动事务和结束事务;而结束事务又有两种方式:提交(表示期间做出的修改将被提交保存)和回滚(回滚至事务启动时刻状态)。事务期间可以保存节点(类似于虚拟机状态保存),当回滚时可以指明回滚至这一状态而非启动事务时候的最初状态。
事务日志与ACID的关系:- redo log 用于保证事务的一致性
- undo log用于保证事务的原子性和隔离性(需要MVCC配合)
- 事务的持久性是又mysql的数据文件,如innodb的 .ibd 文件 保证数据持久性
事务隔离级别与MVCC相关, 后面我们单独介绍MVCC的时候再进行介绍。
事务日志:innodb的事务日志包括: redo log(重做日志) 和 undo log(回滚日志)。但是undo log可不是redo log的逆向过程
- redo log: 是物理日志,记录的是数据页的物理修改语句,不是某一行或几行的修改信息, 它用来恢复提交后的物理数据页,注意这里的恢复数据页只能恢复到最后一次提交的位置。
- undo log: 是逻辑日志, 根据每行数据记录事务执行的链路。
这里引用一个酒店掌柜记账的例子说明 redo log的作用:
酒店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,那么他可以把顾客名和账目写在板上。但如果赊账的人多了,粉板总会有记不下的时候,这个时候掌柜一定还有一个专门记录赊账的账本
如果有人要赊账或者还账的话,掌柜一般有两种做法:
- 直接翻开账本记录(直接写磁盘)
- 先记在粉板(redo log)上,等空闲时再记录到账本(磁盘)上
当生意火爆时,不停有人来要赊账或者还账(更新操作),如果掌柜还是用第一种做法,由于记到账本上需要查找记录(随机读)那就会出现大量的人(更新操作)在等待,会影响工作(阻塞)。
第二种做法,先记在粉板上,空闲时再写回账本。因为记粉板的速度是很快的,就能大量处理赊账或者还账,当掌柜(MySQL)没那么忙的时候,就把粉板上的内容记到账本上。但如果粉板(redo log)写满了,那这时候掌柜(MySQL)就要停下工作,先去把粉板(redo log)的内容写回账本(磁盘)。
两种做法的区别是:
- 第一种是要找到记录后更新,这里涉及随时读,而随时读是很费时间的
- 第二种是记在日志上,这是顺序写的,而顺序写是很快的
因此MySQL采用第二种做法,当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。
这就是WAL(Write-Ahead Logging),先写日志,再写磁盘。
redo log相关的变量:- innodb_flush_log_at_trx_commit={0|1|2} # 指定何时将事务日志刷到磁盘,默认为1。
- 0表示每秒将"log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中。
- 1(默认值)表示每次事务提交都将"log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中,效率较低。
- 2表示每事务提交都将"log buffer"同步到"os buffer"但每秒才从"os buffer"刷到磁盘日志文件中。
- innodb_log_buffer_size:# log buffer的大小,默认8M
- innodb_log_file_size:#事务日志的大小,默认5M
- innodb_log_files_group =2:# 事务日志组中的事务日志文件个数,默认2个
- innodb_log_group_home_dir =./:# 事务日志组路径,当前目录表示数据目录
- innodb_mirrored_log_groups =1:# 指定事务日志组的镜像组个数,但镜像功能好像是强制关闭的,所以只有一个log group。在MySQL5.7中该变量已经移除
这里要特别说明的是innodb_flush_log_at_trx_commit参数设置
- 对数据丢失很敏感,设置为1,保证写到磁盘上。但性能较差。
- 对数据不太敏感,设置为0或2,性能较好。但可能会丢失1秒的数据
以UPDATE 语句为例,看看redo log和binlog的写入过程
mysql> update T set c=c+1 where ID=2;
- 取得 ID=2 这一行(先去内存中找,内存没有从磁盘获取)
- 这行的 c 值加1
- 更新到内存
- 写入 redo log(处于 prepare 阶段)
- 写 binlog
- 提交事务(处于 commit 状态)
上面的流程采用了两阶段提交,目的是为了让binlog 和 redo log的数据逻辑保持一致。
如果上面的update语句在执行的时候出现异常,如mysql宕机了,我们看一下两个日志之间的情况:
- 步骤4之前宕机, 事务提交失败,不会影响数据。内存中的数据会在服务宕机时丢失
- 步骤4完成时宕机,此时已经写入redo log中,重启后发现redo log处于prepare状态, 不恢复数据
- 步骤5完成时宕机,此时已写入binlog中,重启后发现binlog已经写入,就把对应的redo log改为 commit状态。
由此,就可以保证binlog 和 redo log的逻辑一致性。
redo log 与binlog 的区别:- Redo Log是属于InnoDB引擎功能,Binlog是属于MySQL Server自带功能,并且是以二进制 文件记录。
- Redo Log属于物理日志,记录该数据页更新状态内容,Binlog是逻辑日志,记录更新过程。
- Redo Log日志是循环写,日志空间大小是固定,Binlog是追加写入,写完一个写下一个,不 会覆盖使用。
- Redo Log作为服务器异常宕机后事务数据自动恢复使用,Binlog可以作为主从复制和数据恢 复使用。Binlog没有自动crash-safe能力。
innodb每次启动的时候都会进行恢复, redo log记录的是数据页的物理变化,因此在恢复的时候比binlog逻辑日志速度要快很多。另外事务日志具有幂等性,所以多次操作得到同意结果的行为在事务日志中只记录一次。 而二进制日志是逻辑日志,记录的是操作过程不具有幂等性。
undo log:undo log有两个作用: 提供回滚和多版本控制(MVCC)
数据库事务开始之前,会将要修改的记录存放到 Undo 日志里,当事务回滚时或者数 据库崩溃时,可以利用 Undo 日志,撤销未提交事务对数据库产生的影响。
Undo Log产生和销毁: Undo Log 在事务开始前产生;事务在提交时,并不会立刻删除 undo log, innodb 会将该事务对应的 undo log 放入到删除列表中,后面会通过后台线程 purge thread 进行回收处理。Undo Log 属于逻辑日志,记录一个变化过程。例如执行一个 delete , undolog 会记录一个insert ;执行一个 update , undolog 会记录一个相反的 update 。 Undo Log存储:undo log采用段的方式管理和记录。在innodb数据文件中包含一种rollback segment回滚段,每个回滚段中有1024个undo log segment。
undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。
delete/update 操作的内部机制:当事务提交的时候,innodb不会立即删除undo log,因为后续还可能会用到undo log,如隔离级别为repeatable read时,事务读取的都是开启事务时的最新提交行版本,只要该事务不结束,该行版本就不能删除,即undo log不能删除。
但是在事务提交的时候,会将该事务对应的undo log放入到删除列表中,未来通过purge来删除。并且提交事务时,还会判断undo log分配的页是否可以重用,如果可以重用,则会分配给后面来的事务,避免为每个独立的事务分配独立的undo log页而浪费存储空间和性能。
通过undo log记录delete和update操作的结果发现:(insert操作无需分析,就是插入行而已)
- delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。
- update分为两种情况:update的列是否是主键列。
- 如果不是主键列,在undo log中直接反向记录是如何update的。即update是直接进行的。
- 如果是主键列,update分两部执行:先删除该行,再插入一行目标行
- innodb_undo_directory : undo log的独立表空间存放目录
- innodb_undo_logs :回滚段大小
- innodb_undo_tablespaces : undo log 文件个数
undo log 和redo log 想更详细的了解, 推荐文章: 详细分析MySQL事务日志(redo log和undo log) - 骏马金龙 - 博客园



