什么是数据库的事务?
事务是访问并更新数据库中各种数据的一个程序执行单元。事务也是数据库区别于文件系统的一个重要特性。
事务需要满足的特性
1.原子性
原子性就是指数据库中的一个完整的事务是不可分割的工作单位。要么都成功,要么都失败,不能执行一部分。
2.一致性
一致性指事务将数据库从一种状态转变为下一种一致的状态。
所谓一致性状态,就是数据库的数据满足约束的要求。
3.隔离性
事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,也就是一个事务提交对其他事务不可见,可以用锁来实现。
4.持久性
持久性就是事务提交后的结果是永久的,如果发送宕机,也能将数据进行恢复。
持久性保证事务系统的高可靠性,而不是高可用性。高可靠性就是指连续可以正常工作的时间,可用性是指单位时间内,可用的时间比。
事务的分类
1.扁平事务
所有操作都处于同一层次,而且操作是原子的,要么都执行,要么都回滚。
特点:
- 是事务类型最简单的一种,也是实际生产环境中最频繁的事务。
- 不能提交或者回滚事务的某一个部分,或者几个步骤提交。
2.带有保存点的扁平事务
和扁平事务的不同点就是允许事务在执行过程中回滚到该事务之前的一个状态点。
保存点:用来通知系统应该记住事务当前的状态,以便之后能够回到保存点时的状态。一个事务中可以有很多个保存点。
3.链事务
是保存点模式的一种变种。将事务切分为多个串联的事务,当前事务提交之后,将处理的上下文再传给下一个事务,像一个链条一样,一环接一环。
目的: 为了解决保存点事务中,如果发生系统崩溃,保存点都消失的情况。这里将保存点都换成了事务提交,相当于在保存点就将事务提交了,这样就算宕机了,提交了的事务也不会损失。
问题: 链事务的回滚就只能在当前事务中了。所以需要将每个事务中的操作都减少一些。
4.嵌套事务
是一个层次结构框架,由一个顶层事务控制着各个层次的事务。
特点:
- 嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务。
- 叶节点的事务是扁平事务。
- 在根节点的事务是顶层事务,其他事务都是子事务。
- 子事务可以提交也可以回滚,但是提交操作不会马上生效,要等到顶层事务提交才会真正提交。
- 任一事务的回滚都会引起其儿子的回滚。
5.分布式事务
在一个分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。
事务的具体实现
在InnoDB上各个事务级别有其具体的实现方式:
- 锁:隔离性。
- redo log:原子性和持久性。
- undo long:一致性。
1. redo
什么是重做日志?
重做日志用来实现事务的持久性,也就是将产生一些数据库操作保存到日志。这些操作的特点是,其操作结果缓冲区中,并没有写到磁盘中。
重做日志的特点:
- 当触发事务提交时就需要先将该事务的所有日志写到重做日志中进行持久化。
- 重做日志是顺序写的。
如何确保重做日志的持久性?
为了确保每次日志都写入重做日志文件(磁盘),在每次将重做日志缓冲写入磁盘后,存储引擎都需要调用一次fsync操作(将内存数据写到磁盘)。所以,磁盘的性能决定了数据库的性能。
如何通过重做日志提高数据库性能?
用户可以设置重非持久性的情况发生。当事务提交的时候,不马上写入磁盘,而是等一个时间周期之后再执行fsync。这样可以优化日志的写入时间,从而提高数据库的性能。但是如果发生宕机,会丢失一部分未刷新的数据。
重做日志的存储方式
InnoDB的存储引擎中,重做日志缓存和重做日志文件以重做日志块的形式进程存储,一个块的大小为512字节。如果一个页的重做日志大于一个块能够存储的范围,则会被分割为多个块进行存储。
为什么是512字节呢? 因为磁盘的一个扇区大小512字节,可以保证原子性。
重做日志缓冲写到磁盘的触发条件:
- 事务提交时;
- 当日志缓冲的一半空间被使用;
- log checkpoint时。
重做日志恢复数据的原理:
- 不管上次数据库是否正常关闭,都会尝试进行恢复操作。
- checkpoint表示已经刷新到磁盘的日志序列号,所以,只需要恢复checkpoint之后的日志序列号的日志部分即可。
- 执行重做日志。
2.binlog
binlog也就是二进制文件,是数据库上层对已提交了的事务的SQL语句的记录。
binlog和redo log的区别:
- 二者表面上看都是记录了数据库操作的日志,但是本质上有着很大的不同。
- 重做日志是存储引擎层产生的,二进制日志是数据库的上层产生的。二进制日志不仅仅正对引擎层,数据库中任何的更改操作都会产生二进制日志。
- 两种日志记录的内容形式不同。二进制文件是操作逻辑上的,重做日志是物理格式上的。二进制文件对应SQL语句,而重做日志对应引擎对数据页得修改。
- 写入磁盘的时间点不同。二进制日志只在事务提交完成后进行一次写入,而InnoDB存储引擎的重做日志会不断地写入,不随事务提交的顺序影响。
3. undo
什么是undo日志?
用于事务的回滚操作。undo日志中记录了数据之前的版本,当执行回滚操作的时候,利用这些之前的数据信息即可修改到之前的样子。undo日志也用于版本控制(MVCC),当用户读取一行记录的时候,该记录已经被其他事务占用了,当前事务就可以根据事务隔离级别,读取过去相应版本的数据,实现非锁定读,提高并发性能。
undo日志会产生重做日志,因为undo日志也需要持久化。
undo日志的存储方式:
同样采用段的方式进行存储,每个回滚段中记录1024个undo日志段,而在每个undo日志段中进行undo页的申请。也就是undo日志是写入到undo日志段中的undo页。多个事务可以共享一个undo页。
4.purge
用于完成最终的delete和update操作。
当delete操作和update操作提交后,只是将相应记录的flag置为1,并没有删除或者修改记录,真正的操作会延时到purge操作来完成。
为什么需要这样设计,而不是直接操作?
当事务提交时,其他事务可能正在引用这行,如果直接删除会产生错误,所以,能不能删除,就需要purge操作进行判断。如果该技术已经不被其他事务引用,就可以进行真正的删除操作。这就优点像JVM垃圾回收判断机制。
5.group commit
什么是group commit?
顾名思义,就是写一堆数据到磁盘。每次事务提交都需要进行fsync操作,这样不能利用磁盘的效率,如果每次写入一组提交的数据,并进行写优化,减少磁盘IO次数,就能提高性能。
MySQL实现批量二进制日志提交的流程:
数据
- Flush阶段,将每个事务的二进制日志写入内存;
- Sync阶段,将内存中二进制文件写入磁盘,如果有多个事务,那么一次fsync操作就能完成日志写入;
- Cmmit阶段,队头事务根据顺序调用引擎层事务的提交。
事务的隔离级别
SQL标准定义的四个隔离级别:
- 读未提交:会脏读。
- 读已提交:会不可重复读。
- 可重复读:会幻读。
- 串行化:都不会
InnoDB事务隔离级别的特点: InnoDB的默认隔离级别是可重复读,其默认事务隔离级别是可重复读。它的可重复度级别下,通过Next-Key算法,避免了幻读。所以,InnoDB的可重复度就已经达到了串行化的隔离性要求。
分布式事务
什么是分布式事务?
是指允许多个独立的事务资源参与到一个全局的事务当中。
InnoDB提供了对XA事务的支持,并通过XA事务来支持分布式事务的实现。必须将隔离级别设置为串行化,才能使用分布式事务。
XA事务的实现原理
XA事务由一个或者多个资源管理、一个事务管理器和一个应用程序组成:
- 资源管理器:提供访问事务资源的方法。也就是数据库
- 事务管理器:协调参与全局事务的各个事务。就是链接数据库的客户端。
- 应用程序:定义事务的边界,指定全局事务中的操作。
分布式事务两段式提交方式
- 第一阶段,所有餐具全局事务的节点开始准备,告诉事务管理器它们准备好提交了。
- 第二阶段,事务管理器告诉资源管理器执行回滚还是提交。如果任何一个节点不能提交,其他节点都需要回滚。
不好的事务习惯
1.在循环中提交
如果在循环中提交,那么循环很大,就需要提交很多次,每次提交都要重做日志,这样就会大大增加开销。如果在循环最后提交,则只需要写一个重做日志,降低开销。
InnoDB存储引擎会默认自动提交。
2.使用自动提交
开发人员不能很好的掌控事务。应该把事务的控制权交给开发人员。
3.使用自动回滚
如果InnoDB在存储过程中发生错误会自动执行回滚操作。
使用自动回滚无法张掌控准确信息。
长事务
长事务就是执行时间较长的事务。
长事务一旦执行失败,就需要回滚,会造成很大的损失,所以,需要将其切分为很多给小事务。