事务的四大特性
- 原子性: 要么都成功,要么都失败
- 一致性: 执行事务前后数据保持一致,多个事务对同一个数据库读取的结果是相同的
- 隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,个并发事务直接数据库是独立的
- 持久性: 一个事务被提交后,对数据库中数据的改变是持久的.数据库发生故障也不会对其有任何改变
并发事务带来的问题
- 脏读: 读取到了另一个事务还没提交的数据
- 丢失修改: 两个事务同时读取并修改数据,后面提交的覆盖了前面提交的数据
- 不可重复读: 单个事务内多次读取同一数据,在这个事务还没结束时,另一个事务也访问了该数据,并修改了该数据,导致第一个事务两次读取的数据不太一样
- 幻读: 与不可重复读类似.第一个事务读取了部分数据,第二个事务提交了部分数据,然后第一个事务再次查询发现多了几条不存在的数据.跟发生了幻觉一样
不可重复的和幻读区别在于不可重复读是读取到了另一个事务的修改值,幻读是读取到了另一个事务新增或删除的记录
事务的隔离级别
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,会导致脏读,幻读,不可重复读
- READ-COMMITTED(读取已提交): 允许读取并发事务已提交的数据,可以阻止脏读,幻读和不可重复读可能发生
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果是一致的,除非数据本身被事务自己所修改,可以阻止脏读,不可重复读,幻读可能发生(MySQL数据库默认级别)
- SERIALIZABLE(可串行化): 最高隔离级别,所有事务逐个执行.可以阻止脏读,幻读,不可重复读(Oracle数据库默认级别)
MVCC
多版本并发控制, 是一种并发控制的方法,实现对数据库的并发访问, 在 InnoDB 存储引擎实现隔离级别的一种具体方式.用于实现提交读和可重复读这两种隔离级别. 未提交读总是读取最新的数据,无需使用 MVCC, 串行化隔离级别需要对所有读取的行加锁,单使用 MVCC无法实现
MVCC就是为了实现读写冲突不加锁, 而这个读指的就是快照读, 而非当前读, 当前度实际上是一种加锁的操作, 是悲观锁的实现
当前读
读取的是记录的最新版本,读取是还要保证其他并发事务不能修改当前记录, 会对读取的记录进行加锁
快照读
不加锁的 select 操作就是快照读, 即不加锁的非阻塞读. 前提是隔离级别不是串行级别, 串行级别下的快照读会退化成当前读
隐藏字段
- 最近修改插入事务 ID
- 回滚指针, 指向这条记录的上一个版本
- 隐藏主键, 如果数据表没有主键, InnoDB 会自动产生一个聚簇索引
undo 日志
- insert undo log
- 代表事务在 insert 新纪录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
- update undo log
- 事务在进行 undate 或 delete 是产生的 undo log, 不仅在事务回滚时需要, 在快照读时也需要; 所以不能随便删除, 只有在快速读或事务回滚不涉及改日志时, 对应的日志才会被purge 线程统一清除
Read View
事务进行快照读操作的时候产生的读试图, 在该事务执行的快照读的那一刻, 会生成数据库系统当前的一个快照, 记录并维护系统当前活跃事务的ID(每个事务开启时, 会分配一个ID, 最新的事务, ID值越大)
MVCC 原理
依赖记录中的隐藏字段, undo 日志, Read View 来实现. 主要将要被修改的数据的最新记录中的当前事务id取出来,与系统当前其他活跃事务的ID去对比,如果跟 Read View 的属性作了比较, 不符合可见性, 就通过回滚指针去去除 undo log 中的当前事务 ID 在比较,遍历链表中的事务ID,直到找到满足特定条件的事务ID, 那么这个当前事务ID所在的旧记录就是当前事务能看见的最新老版本