前言
背景:之前提到MySQL可以并发操作,即多个MySQL客户端操作一个服务端中的数据库,因此涉及到并发访问的问题,所以就有事务的概念。
定义:事务的本质是并发控制的单元,是用户定义一系列数据库操作的集合。这些操作要么都做,要么都不做,是一个不可分割的工作单位。
组成:事务可由一条非常简单的 SQL 语句组成,也可以由一组复杂的 SQL 语句组成;MySQL innodb 下,单条语句都具备事务;可以通过 autocommit = 0; 设置当前会话手动提交;
事务具有ACID特性,即原子性、一致性、隔离性和持久性。
ACID特性
原子性(A)
事务操作要么都做(提交),要么都不做(回滚);事务是访问并更新数据库各种数据项的一个程序执行单元,是不可分割的工作单位;通过 undolog 来实现回滚操作。undolog 记录的是事务每步具体操作,当回滚时,回放事务具体操作的逆运算;
事务包含的全部操作是一个不可分割的整体,要么全部执行,要么全部不执行;
虽然说事务要保持原子性,例如完整的串行执行就可以保证事务中指令执行的原子性;但是这种方式的性能是非常低的,因此Mysql通常会通过事务与事务之间指令的交叉执行来提高性能。
隔离性(I)
描述:各个事务之间互相影响的程度;防止多个并发事务交叉执行导致的数据不一致,通过锁和MVCC实现。
目的:主要规定多个事务访问同一数据资源,各个事务对该数据资源访问的行为,不同的隔离性是应对不同的现象(脏读、不可重复读、幻读);
事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,并发事务之间不会相互影响,设定了不同程度的隔离级别,通过适度破环一致性,得以提高性能;通过 MVCC 和 锁来实现;MVCC 是多版本并发控制,主要解决一致性非锁定读,通过记录和获取行版本,而不是使用锁来限制读操作,从而实现高效并发读性能。锁用来处理并发 DML 操作;数据库中提供粒度锁的策略,针对表(聚集索引 B+ 树)、页(聚集索引 B+ 树叶子节点)、行(叶子节点当中某一段记录行)三种粒度加锁;
持久性(D)
事务一旦完成,要将数据所做的变更记录下来,包括数据存储和多副本的网络备份;
事务提交后,事务 增删改操作将会持久化(写入 redolog 磁盘文件 哪一个页 页偏移值 具体数据);即使发生宕机等故障,数据库也能将数据恢复。redolog 记录的是物理日志;
一致性(C)
事务的前后,所有的数据都保持一个一致的状态,不能违反数据的一致性检测(完整性约束检查);
一致性指事务将数据库从一种一致性状态转变为下一种一致性的状态,在事务执行前后,数据库完整性约束没有被破坏;一个事务单元需要提交之后才会被其他事务可见。例如:一个表的姓名是唯一键,如果一个事务对姓名进行修改,但是在事务提交或事务回滚后,表中的姓名变得不唯一了,这样就破坏了一致性;一致性由原子性、隔离性以及持久性共同来维护的。
隔离级别
在多个事务并发执行时,需要考虑隔离级别,即不同的隔离性。ISO 和 ANIS SQL 标准制定了四种事务隔离级别的标准,隔离级别越高,性能越低。
即READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE前三种的隔离级别会导致不同的并发读异常,即脏读、不可重复读、幻读场景。
不同的隔离级别产生的不同的并发读异常情况:
脏读:一个事务读到另一个未提交事务修改的数据;
不可重复读:一个事务两次读取同一个数据不一样;
幻读:一个事务内两次读到同一范围内的记录得到的结果集不一样;
区别
脏读和不可重复读的区别在于,脏读是读取了另一个事务未提交的数据,而不可重复读是读取了另一个事务提交之后的修改;本质上都是其他事务的修改影响了本事务的读取;
不可重复读和幻读比较类似;不可重复读是两次读取同一条记录,得到不一样的结果;而幻读是两次读取同一个范围内的记录得到的结果集不一样(可能不同个数,也可能相同个数内容不一样,比如x一行后又添加新行);不可重复读是因为其他事务进行了 update 操作,幻读是因为其他事务进行了 insert或者 delete 操作。
MySQL innodb默认支持的隔离级别是 REPEATABLE READ;
READ UNCOMMITTED
读未提交;该级别下读操作不加锁,写操作加排他锁,写锁在事务提交或回滚后释放锁;所以可能会读到事务回滚前的数据,会产生脏读。
READ COMMITTED
读已提交(RC);该隔离级别下读取历史版本的最新数据,所以读取的是已提交的数据;会产生不可重复读的现象。
REPEATABLE READ
可重复读(RR);此时读取操作读取事务开始时的版本数据;会产生幻读的现象。可以用当前读,即给读加锁来解决幻读现象。
SERIALIZABLE
可串行化;该级别下给读加了共享锁;所以事务都是串行化的执行;此时隔离级别最严苛;
共享锁&排他锁
为了解决并发读异常问题,数据库引入了锁机制。锁分为共享锁和排他锁,共享锁允许多个事务同时读取同一份数据,但不能进行修改;排他锁则只允许一个事务修改数据。
MVCC
MVCC是一种多版本并发控制技术,可以通过不加锁的情况下用于解决并发读异常和幻读问题。MVCC通过为每个事务创建一个独立的版本来实现隔离性,每个版本都包含该事务执行时所见到的所有数据。当多个事务同时访问同一份数据时,系统会根据每个事务的版本号来判断是否允许访问。
多版本主要依赖Undo-log日志来实现,而并发控制则通过表的隐藏字段+ReadView快照来实现
详细内容参考:https://maimai.cn/article/detail?fid=1760762705&efid=UVf-Pw63L4lRkESvTUv1AQ
read view
在 read committed 和 read repeatable 隔离级别下,MVCC 采用 read view 来实现的,它们的区别在于创建 read view 时机不同:
read committed 隔离级别会在事务中每个 select 都会生成一个新的 read view,也意味着在同一个事务多次读取同一条数据可能出现数据不一致;存在不可重复读情况。因为在多次读取期间可能有其他事务修改了该条记录,并提交了;
read repeatable 隔离级别是启动事务时生成一个 read view,在整个事务读取数据才使用这个 read view,这样保证了在事务期间读到的数据都是事务启动前的记录,解决了不可重复读。
redo
redo 日志用来实现事务的持久性;内存中包含 redo log buffer,磁盘中包含 redo log file;
当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的commit 操作完成才完成了事务的提交;
redo log 顺序写,记录的是对每个页的修改(页、页偏移量、以及修改的内容);在数据库运行时不需要对 redo log 的文件进行读取操作;只有发生宕机的时候,才会拿redo log 进行恢复;
undo
undo 日志用来帮助事务回滚以及 MVCC 的功能;存储在共享表空间中;undo 是逻辑日志,回滚时将数据库逻辑地恢复到原来的样子,根据 undo log 的记录,做之前的逆运算;比如事务中有 insert 操作,那么执行 delete 操作;对于 update 操作执行相反的 update 操作;
同时 undo 日志记录行的版本信息,用于处理 MVCC 功能;