MySQL数据库中的事务详解!详细分析事务的四大特性的实现

157 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

基本概念

  • 事务的四大特性ACID :
    • 原子性Atomic : 事务的所有的SQL操作作为一个原子工作单元执行.要么全部执行,要么全部不执行
    • 一致性Consistent : 事务完成后,所有数据状态都是一致的
    • 隔离性Isolation : 如果有多个事务并发执行,那么每个事务做出的修改必须和其余的事务隔离
    • 持久性Duration : 事务完成后,对数据库中数据的修改会持久化存储
  • 事务的四种隔离级别:
隔离级别脏读不可重复读幻读
未提交读Read Uncommitted可能可能可能
已提交读Read Committed不可能可能可能
可重复读Repeatable Read不可能不可能可能
序列化Serializable不可能不可能不可能

原子性实现

  • MySQL数据库事务的原子性通过undo log实现
    • 事务的所有的增删改的修改操作的相反操作都会写入undo log中.比如事务执行一条insert语句,那么undo log就会记录一条对应的delete语句
    • undo log是一个逻辑文件,记录的是相对应的SQL语句.如果事务执行发生异常,导致事务无法成功提交,系统就会执行undo log中相对应的撤销操作,达到事务回滚的目的
    • undo log可以实现MVCC的多个版本并发控制

隔离性实现

已提交读

  • 已提交读Read Committed允许可重复读的实现策略:
    • 数据的读取不加锁,数据的写入,修改,删除需要加锁
    • 可以解决脏读的问题,无法避免不可重复读的问题 在这里插入图片描述
  • 使用加锁策略后,不存在读取到脏数据的情况:
    • T1写数据x时,首先获取x的锁,导致T2的读操作等待
    • T1进行数据回滚后,释放锁 ,T2可以继续读取原来的数据

可重复读

  • 可重复读Repeatable Read允许幻读的实现策略 : MVCC多个版本行控制策略 在这里插入图片描述
  • 这种情况下会出现T1的更新操作后,导致T2两次读取的数据不一致:
    • 通过加行级锁无法解决读取数据不一致的问题:
      • T2首先读取x
      • T1经过加锁,解锁的步骤,更新x的值,提交事务
      • T2继续读取,读取出来的值是T1更新后的值
      • 这样T2两次读取的结果不一致
  • MVCC多版本行控制:
    • 行级锁是一个悲观锁 ,MVCC是一个乐观锁.乐观锁可以在一定程度上避免加锁操作,开销更低
    • InnoDBMVCC实现是通过保存数据在某个时间点的快照来实现的
    • 一个事务,不管执行多长时间,内部看到的数据是一致的.也就是说,事务在执行过程中不会相互影响
  • MVCC多版本行控制的具体实现:
    • 通过在每行记录后面保存两个隐藏的列来实现多版本行控制
    • 一个列保存了行的创建时的系统版本号
    • 一个列保存的行的过期时的系统版本号,也就是删除时的系统版本号
    • 每次开始一个新的事务,系统版本号就会递增
      • SELECT操作:
        • 只会查找版本早于或者等于当前事务版本的数据行.这样可以保证事务读取的行,要么是事务开始前就存在的行,要么是事务自身插入或者修改的行
        • 行的删除版本要么是未定义版本号,要么大于当前事务版本号.这样可以保证事务读取的行,在事务开始之前未删除
      • INSERT操作:
        • 将新插入的行保存当前版本号为行版本号
      • DELETE操作:
        • 将删除的行保存当前版本号为删除标识
      • UPDATE操作:
        • UPDATE操作作为DELETE操作和INSERT操作的组合
          • DELETE操作保存当前版本号到原来的行作为删除标识
          • INSERT操作的行保存当前版本号作为行版本号
  • MVCC多版本行控制策略可以确保一个事务中读取的是同一个数据库版本快照

持久性实现

  • MySQL数据库事务的持久性是通过redo log实现的:
    • 事务的所有增删改的修改操作,数据库都会生成一条redo日志到redo log
    • redo log区别于undo log中记录的SQL语句 ,redo log记录的是事务对数据库的哪个数据页做了什么样的修改,属于物理日志
  • redo日志的使用场景:
    • 数据库系统直接崩溃,需要进行恢复
    • 通常情况下,数据库都会按时间点备份的策略,首先将数据库恢复到最近备份的时间点状态,然后读取这个时间点之后的redo log记录,重新执行相应记录,达到最终恢复的目的

日志文件刷新策略

  • 日志文件undo log和redo log不是直接写入到磁盘中,而是写入log buffer,然后等待合适的时机同步到OS buffer,然后由操作系统决定刷新到磁盘的时间 在这里插入图片描述
  • MySQL主要有三种日志刷新策略:
    • 每次事务提交写入OS buffer, 并调用fsync刷新到磁盘中
    • 每秒写入OS buffer, 并调用fsync刷新到磁盘中
    • 每秒提交写入OS buffer, 然后每秒调用fsync刷新到磁盘中
  • MySQL默认使用第一种日志刷新策略,安全性依次下降,效率依次上升