并发事务问题
在多个事务并发运行时,会遇到操作相同的数据来完成各自的任务,可能会存在一下问题
脏数据:
当一个事务正在访问数据并且对该数据进行了修改,但这个操作还没有提交到数据库中,这时另一个事务也访问了这个数据,并且使用了该数据,因为数据还没有提交到数据库中,那么另一个事务读到的这个数据就是脏数据,依据脏数据所有操作是不正确的
示例;
总结:A事务读取到到B事务尚未提交的更改数据,并在这个脏数据的基础上进行操作,如果B事务进行回滚,A事务读取到的事务是不被承认的
不可重复读(UnreoeatableRead)
A事务内多次读取同一数据,在A事务还没结束时,B事务也访问该数据,A事务在两次读取数据之间,B事务修改了该数据并且提交了该数据。因此,A事务再次读取数据时可能与第一次读取的数据不太一样。一个事务内两次读取到的数据是不一样的,因此称为不可重复读。
示例:
总结:一个事务内两次读取到的数据是不一样的。不可重复读是指A事务读取到B事务已经提交的更改过的数据。
幻读(phantom Read)
A事务读取数据,接着B事务插入了一些数据,在随后的查询中,A事务会发现原本不存在的记录,好像发生了幻觉,所以称为幻读。
示例:
总结:A事务读取到B事务提交的新增数据,幻读一般出现在数据统计的事务中
不可重复读和幻读的区别:
不可重复读针对的是行级别的数据的更改,重点是更改数据
幻读针对的是表级别的新增/删除数据,在统计事务中,两次读取的数据统计不一样,重点在于新增或者删除
事务隔离级别
SQL标准定义了四种隔离级别
1.读取未提交:Read-Uncommitted
最低的隔离级别,允许读取尚未提交的更改数据,可能会导致脏读、幻读、不可重复读
2.读取已提交:Read-Commited(RC)
允许读取并发事务中已经提交的数据,可以避免脏读,但是幻读、不可重复读仍然可能发生
3.可重复读:Repeatable-READ(RR)
对同一个字段的多次读取结果都是一致的,除非数据被事务自身修改,可以避免脏读,不可重复读,但幻读仍然可能发生
4.串行化:Serlializable
最高隔离级别,完全服从ACID的隔离级别,所有的事务依次逐个进行,这样事务之间完全不可能出现干扰,即串行化可以避免脏读、不肯重复读和幻读
通过 select @@tx_isolation 可以查看隔离级别
默认的隔离级别是可重复读
隔离级别如何实现?
读取已提交和不可重复读采用的是排它锁(行锁)
读取已提交隔离级别是在读(写)之前加锁,在读(写)之后解锁,而不是等事务结束解锁,存在不可重复读的问题
不可重复读隔离级别是整个事务都处于加锁状态下,只有事务结束才解锁,但存在幻读,因为使用的是行锁,可以增加行等行为
串行化使用的是表锁,锁定的是整张表,可以解决幻读问题
事务原理、
1.MVCC多版本控制机制
MVCC(MultiVersion Concurrency Control),即多版本控制技术,也就是将行锁和行的多版本结合,只需要很小的开销,就可以实现事务,能够提高数据库的并发性能
MVCC提供了另一种方式来实现读取已提交和可重复读,相比于锁的方式效率更高一些
MVCC实现原理
对于数据表的每一条数据的记录
- DATA_TRX_ID:标记最新更新这条记录的transactionID,每处理一个事务,其值自动加1,大小是6字节
- DATA_ROLL_PTR:指向当前记录的rollback segment的undo log记录,找到之前版本的数据就是通过这个指针
- DB_ROW_ID:行标识(隐藏自增ID),在innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则聚集索引中是不包含这个值,这个用在索引当中
当针对一条数据记录执行update操作时,操作如下:
- 针对表的记录加设排它锁
- 把这条记录的最新记录拷贝到undo log日志中,DATA_TRX_ID和DATA_ROLL_PTR不变
- 通过修改生成新的记录,包含新的DATA_TRX_ID和DATA_ROLL_PTR
- 通过往复操作就会形成一个版本链
当多个事务执行并发操作时,不同的隔离级别处理也不同
- 读未提交:直接读取新版本链最新记录
- 串行化:通过互斥锁来访问数据
- 读已提交和重复读:通过基于版本链的ReadView
ReadView:在执行查询SQL时会生成ReadView,它是由所有的事务ID数组(最小min_id)和已创建事务id(max_id)组成
如果tid<min_id,表示这个版本事务是已经提交的,则数据可见
如果tid>max_id,表示这个版本是将来要启动的事务,是不可见的
如果min_id<=tid<=max_id,有两种情况,如果tid在事务中,不可读取,则通过undo log找到它的上个版本,如果tid不在事务中,则找到已提交事务
不同的隔离级别在于生成的ReadView的时间点不同
读已提交:一个事务中每条select语句开始
可重复读:一个事务中第一个查询语句开始
MVCC特点:
1.每行数据都存在一个版本,每次数据更新时都会更新该版本
2.修改时复制出当前版本可随意修改,各个事务之间互不干扰
3.保存时比较版本号,如果成功则覆盖原纪录,失败则放弃copy
4.事务是以排它锁的形式修改原数据
5.将修改前的数据放在undo log中,通过回滚指针与主数据相关联
6.修改失败,则通过undo log中数据进行恢复
2.事务日志
在MVCC中,要保证事务的执行用到了undo log和redo log,需要通过事务日志来做跟踪进行保证事务的ACID特性
每一个数据在写入数据库之前,先写入到日志文件中,,如果要删除会先到日志文件中将对应标志位删除,但数据库并没有变化,只有在整个事务提交时,再把整个事务中的SQL语句批量同步到磁盘的数据库文件,每一次写操作都需要执行两边:
1.先写入日志文件中,仅仅是操作过程,而不是操作数据本身,所以速度比写到数据库文件中速度要快
2.然后在写入到数据库文件中,写入数据库文件中的操作是重做事务日志中已经提交的事务操作记录
2.1 redo log
在innodb存储引擎中,事务日志通过重做日志(redo log)和innodb存储引擎的日志缓冲区来实现。事务开启时,事务的操作,都会先写入存储引擎的日志缓冲区,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上进行持久化
2.2 undo log
undo log 主要为事务回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log,undo log记录数据在每个操作前的状态,如果事务在执行过程中需要回滚,就可以根据undo log进行回滚操作,单个事务的回滚只会回滚当前事务的操作,而不会影响其他事务的操作
redo log (记录操作过程)保证事务的持久性和一致性,而undo log(记录上一条数据)保证事务的原子性