我们都知道事务的4个特性,原子性 一致性 隔离性和持久化
事务具有4个特征,分别是原子性、一致性、隔离性和持久性,简称事务的ACID特性;
一、原子性(atomicity)
一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性
二、一致性(consistency)
事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。
如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态
三、隔离性(isolation)
事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,分别是:未授权读取,授权读取,可重复读取和串行化
1、读未提交(Read Uncommited),该隔离级别允许脏读取,其隔离级别最低;比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交,此时,事务B能够看到这个数据项在事务A操作过程中的所有中间值(如1变成2,2变成3等),而对这一系列的中间值的读取就是未授权读取
2、授权读取也称为已提交读(Read Commited),授权读取只允许获取已经提交的数据。比如事务A和事务B同时进行,事务A进行+1操作,此时,事务B无法看到这个数据项在事务A操作过程中的所有中间值,只能看到最终的10。另外,如果说有一个事务C,和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B也同样可以读取到20,即授权读取允许不可重复读取。
3、可重复读(Repeatable Read)
就是保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻是一致的,因此该事务级别禁止不可重复读取和脏读取,但是有可能出现幻影数据。所谓幻影数据,就是指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。在上面的例子中,可重复读取隔离级别能够保证事务B在第一次事务操作过程中,始终对数据项读取到1,但是在下一次事务操作中,即使事务B(注意,事务名字虽然相同,但是指的是另一个事务操作)采用同样的查询方式,就可能读取到10或20;
4、串行化
是最严格的事务隔离级别,它要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行。
四、持久性(durability)
一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。--即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态
Mysql的mvcc机制是多版本并发控制主要是针对innodb的读已提交和可重复度,不支持读未提交和串行化(将所有的操作串行化执行,效率低)。
多版本控制(Multiversion Concurrency Control): 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。在内部实现中,InnoDB通过undo log保存每条数据的多个版本,并且能够找回数据历史版本提供给用户读,每个事务读到的数据版本可能是不一样的。
Mysql利用mvcc+悲观锁和mvcc+乐观锁,提高数据库的整体性能mvcc解决读写冲突,锁解决了谢谢冲突。
当前读和快照读
• 当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
• 快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
Mvcc的几个概念
隐藏字段
(1) 事务id:DB_TRX_ID,表示最近一次对本记录行作修改(insert | update)的事务ID。至于delete操作,InnoDB认为是一个update操作,不过会更新一个另外的删除位,将行表示为deleted。并非真正删除。
(2) 回滚指针: DB_ROLL_PTR, 回滚指针,指向当前记录行的undo log信息,也就是记录行的历史版本
(3) 行号:DB_ROW_ID ,如果定义了主键,那么InnoDB会使用主键作为聚簇索引,如果没有定义主键,那么会使用第一非空的唯一索引(NOT NULL and UNIQUE INDEX)作为聚簇索引,如果既没有主键也找不到合适的非空索引,就会将DB_ROW_ID作为主键,自增,占6个字节.
Read View 读视图也叫快照
(1) trx_ids 活跃事务ID列表
(2) low_limit_id 最大的事务ID+1
(3) 活跃事务列表trx_ids中最小的事务ID
Undo log日志
存储的是老版本数据,当一个事务需要读取记录行时,如果当前记录行不可见,可以顺着undo log链找到满足其可见性条件的记录行版本。是通过回滚指针形成的链表结构。
① insert undo log : 事务对insert新记录时产生的undo log, 因为不存在正在对这行数据进行读的事务,所以这个日志只在事务回滚时需要, 所以在事务提交后就可以立即丢弃。
② update undo log:: 事务对记录进行delete和update操作时产生的undo log,不仅在事务回滚时需要,快照读也需要,因为可能存在正在对这行数据进行读的事务,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被purge线程删除。
Purge线程
从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。
Undo log的图解
Mvcc工作的大致流程
在innodb中,创建一个新的事务新事务后,执行第一个select语句的时候,innodb会创建一个快照(read view),快照中会保存系统当前不应该被本事务看到的其他活跃事务id列表(即trx_ids)。当用户在这个事务中要读取某个记录行的时候,innodb会将该记录行的DB_TRX_ID与该Read View中的一些变量进行比较,判断是否满足可见性条件
假设当前事务要读取某一个记录行,该记录行的DB_TRX_ID(即最新修改该行的事务ID)为trx_id,Read View的活跃事务列表trx_ids的上下界分别为 low_limit_id 和 up_limit_id.
具体的比较算法如下:
- 如果 trx_id < up_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之前就提交了,所以该记录行的值对当前事务是可见的。直接标识为可见,返回true,
- 如果 trx_id >= low_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之后才被创建且修改该行的,所以该记录行的值对当前事务不可见。应该通过回滚指针找到上个记录行版本,判断是否可见。循环往复,直到可见
-
如果 up_limit_id <= trx_id < low_limit_id, 那就得通过二分查找判断trx_id 是否在trx_ids列表出现过,
① 如果出现过,说明是当前read view 中某个活跃的事务提交了,那当然是不可见的,应该通过回滚指针找到上个记录行版本,判断是否可见,循环往复,直到可见
② 如果没有出现过,说明这个事务是已经提交了的,标识为可见,返回true
4.RR和RC的Read View的实现过程区别
提交读和可重复读都是使用 MVCC 机制来实现的,但是实现过程略微有一些不同,
①在innodb中的Repeatable Read级别, 只有事务在begin之后,执行第一条select(读操作)时, 才会创建一个快照(read view),将当前系统中活跃的其他事务记录起来;并且事务结束前都是使用的这个快照,不会重新创建,直到事务结束。
②在innodb中的Read Committed级别, 事务在begin之后,执行每条select(读操作)语句时,快照会被重置,即会重新创建一个快照(read view)。
所以这个实现的差别可以达到不同的隔离级别,两个隔离级别都是快照读