ACID

  1. 原子性
  2. 一致性
  3. 隔离性
  4. 持久性

原子性

  1. 基于日志的REDO/UNDO机制
  2. undo log (回滚日志):用作回滚
    • 提供回滚操作,一般是逻辑日志
    • 主要有两个作用:回滚、MVCC;
    • 为了实现事务原子性,要么全部完成commit,要么全部回滚rollback,Innodb用于实现mvcc(多版本并发控制)
    • innodb为每行记录都实现了三个隐藏字段:隐藏di6字节的事务di7字节的回滚指针
    • 每次update操作,都会进行以下过程:用排它锁锁定该行、记录redo log、把改行修改前的值copy到undo log、修改当前行的内容(事务id就是当前事务的id,回滚指针指向undo log中的上一个版本的行数据)
    • 再有其他update操作会重复上述过程,从而在undo log 里形成一条由回滚指针连接起来的链表
    • innodb中存在purge线程,会定时清理那些比现在最老的活动事务id还小的undo log,从而保证undo log 不会无限增长;
    • 回滚时需要根据回滚指针找出当前事务修改前的版本
    • insert操作没有上一个版本,所以不需要往undo log里复制内容
    • delete并不是真正的删除,只是打了一个删除的标记,真正的删除是purge线程完成的
    • 一旦事务完成,所有修改必须被记录下,保证数据一致性(后面redo log解释)

隔离性

  1. 主要通过以下两种手段实现
    • 多版本并发控制(MVCC)
      • 可以在提高隔离级别的情况下,有效提高数据库并发能力;如果没有MVCC,为了提高隔离级别只能在读操作上加共享锁,所以导致在其他事务为数据加上排他锁的情况下无法读取数据(排他锁和共享锁是互斥的)
      • InnoDB对MVCC的实现方式是,在需要时,通过回滚日志构造出历史版本快照!
      • InnoDB通过为每行数据后添加两个额外的隐藏值来实现MVCC,这两个值分别用来记录这行数据何时被创建,以及何时过期(被删除),实际操作中记录的并不是时间,而是事务版本号,每开启一个新事务,事务的版本号就会递增。
      • 共享锁(S锁):若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
      • 排他锁(X锁):若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。
  2. 四种隔离级别
    • 未提交读
      • 读不加锁,写加排他锁,直至事务结束。
      • 脏读:因为读不加锁,所以可以读取到其他事务还没有提交的数据,如果其他事务执行了回滚操作,此时读取的数据就是脏数据
    • 已提交读
      • 读不加锁(MVCC),写加排他锁,直至事务结束。
      • 通过MVCC机制获取最新快照,如果事务还没提交,这里的最新快照指的是事务开始前的数据,所以是不会读到其他未提交事务的中间数据的,从而解决了脏读。
      • 不可重复读:一次事务内的两次读取数据不一致
      • 原因是在两次读取之间另外一个事务对数据进行了修改并commit。由于在这种隔离级别下,每次 select 都会通过 MVCC 获取当前数据的最新快照,所以会导致不可重复读。
    • 可重复读
      • 读不加锁(MVCC),写加排他锁,直至事务结束。
      • 通过调整MVCC快照的生成时机,即一个事务只会在第一次 select 时生成数据快照,后续的 select 都是在这个快照之上进行的,所以无论其他事务是否提交,事务中的两次读取都是相同的,从而解决了不可重复读!
      • 幻读:幻读有两种理解方式
        • 两次读取不一致,读取到了其他已提交事务的新增数据(不可重复读指的是读到了修改或删除数据);但是,这种幻读的理解方式在mysql 的 innodb 搜索引擎下是不存在的,因为已经通过多版本并发控制(MVCC)解决了这种意义上的幻读!
        • 另外一种理解是,同一个事务内,前一次读操作和后一次写操作数据不一致,比如前一次读操作读到的是0,后一次insert操作却因为其他事务在两次操作之间更改数据并提交,数据不为0 从而导致 insert失败或对insert造成其他影响!。
        • 发生这种情况的原因是MVCC只对读有效,对写无效!
    • 可串行化
      • 读加共享锁,写加排他锁!
      • 不会有任何问题!

持久性

  1. 通过redo log实现
  2. redo log (重做日志):用来恢复提交后的物理页数据(持久化)
    • 是物理日志,记录的是数据库中每个页的修改
    • 包括两部分:1)内存中的日志缓冲(redo log buffer);2)磁盘上的重做日志
    • 通过force log at commit机制实现事务的持久性;
    • InnoDB存储引擎层的日志。

一致性

  1. 一个事务必须使数据库从一个一致性状态变换到另一个一致性状态。
  2. 原子性和一致性的区别:即便原子性保持了,也可能因为隔离性的原因导致原子操作前后两次的状态不一致。
  3. 前面三个特性实现之后,一致性自然也实现了。

补充,MVCC在可重复读隔离级别下的增删改查

  1. SELECT时,读取创建版本号 <= 当前事务版本号,删除版本号为空或 > 当前事务版本号。
  2. INSERT时,保存当前事务版本号为行的创建版本号。
  3. DELETE时,保存当前事务版本号为行的删除版本号。
  4. UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行。

reference

  1. https://blog.csdn.net/Vincent2014Linux/article/details/89669762
  2. https://www.jianshu.com/p/563612576e6e