MySQL事务学习笔记

147 阅读6分钟

事务

一系列的sql语句,要么全成功,要么就全失败。

特性

ACID

  • 原子性:事务是不可分割的最小单元,一系列连续的sql操作 失败了一个 就需要把前面的全部回滚。
  • 一致性:执行事务前后,数据满足完整性约束,数据总量保持一致。
  • 隔离性:多个用户并发访问数据库时,数据库为每一个用户开启的事务不能被其他事务干扰,多个事务之间相互隔离 不受并发影响 独立执行。
  • 持久性:一个事务一旦提交,它对数据库的操作就是永久的。

InnoDB

  • 通过undo log保证事务原子性;
  • 通过MVCC / 锁机制保证事务隔离性;
  • 通过redo log保证事务持久性;
  • 原子性+隔离性+持久性 保证 事务一致性;

并发事务问题

  • 脏读:一个事务读取到数据是另一个事务没有提交的数据。 比如事务A对某个数据进行修改,B读取到A修改后的数据。但是A做了回滚操作 没有完成提交,B读取到的数据就是脏数据。
  • 不可重复读:一个事务读取两次某个数据,在这两次读取中 有其他事务对这个数据进行了修改,导致两次读取出来的数据不一致。在同一个事务中,两次读取得到不同的数据,就是不可重复读。
  • 幻读:幻读和不可重复读有点类似,但是幻读强调的是数据的增减 而不是数据的更新。比如,一个事务查询某个数据时 没有找到,插入该数据时,又发现这个数据已经存在了。

事务的隔离级别

  • 读未提交:所有的事务都可以看到其他没有提交的事务的执行结果,只能防止第一类更新丢失(事务A回滚 覆盖合法的事务B提交),不能解决脏读、不可重复读、幻读。 (第一类更新丢失,e.g. x=100,A修改x= 50, B操作x += 100,提交为x=150,A回滚后 x=50。如果是第二类更新丢失,A回滚后,100+x(100),最终是x=200。)
  • 读已提交RC:一个事务的更新结果只有在事务提交以后,另一个事务才能读取到数据更新的结果。不能解决不可重复读和幻读。
  • 可重复读(MySQL默认隔离级别):可重复读是快照读(快照读仅保证读取历史版本的数据,但无法阻止其他事务插入新记录到查询范围内),一个事务多次读取同一个数据,实际上读取的是数据快照,其他事务的修改对当前事务是不可见的 可以保证在同一事务内多次读取到的数据是一致的。 可以防止第一类更新丢失、第二类更新丢失、脏读、不可重复读,但不能解决幻读。
  • 串行化:事务只能一个接一个的执行,不能并发执行。可能导致大量的超时现象和锁竞争。

隔离级别怎么实现

MySQL的隔离级别通过锁和MVCC机制共同实现,串行化隔离级别通过锁来实现,其他隔离级别基于MVCC实现 但是也可能用到锁机制 比如可重复读在当前读下需要加锁保证不出现幻读。

MVCC

当前读与快照读

  • 当前读

    读取的是记录的最新版本,读取时需要加锁保证其他事务不能修改当前记录。select ... lock in share modeselect ... for updateupdateinsertdelete都是当前读。

  • 快照读

    简单的不加锁的select就是快照读。快照读读取的是记录数据的可见版本,有可能是历史版本,不加锁,是非阻塞读。

    • Read Committed:每次select都会生成一个快照读。
    • Repeatable Read:开启事务后第一个select语句是快照读的地方。
    • Serializable:快照读退化为当前读。

MVCC多版本并发控制,维护一个数据的多个版本,使得读写操作没有冲突,快照读为MVCC提供了一个非阻塞读功能。MVCC具体实现依赖于数据库记录中的三个隐式字段、undo log日志、readView。

  • 隐式字段

    • DB_TRX_ID:最近修改事务id,记录插入这条记录或最后一次修改这条记录 的事务id。
    • DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,配合undo log。
    • DB_ROW_ID:隐藏主键,如果表结构没有指定主键,就生成该隐藏字段。
  • undo log

    • undo log日志:在insert、update、delete的时候产生的便于回滚的日志。
      • insert的时候,产生的undo log日志只在回滚的时候需要,事务提交后,可以立即删除。
      • update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读的时候也需要,不能立即删除。
    • undo log版本链:不同事务或者相同事务对同一条记录进行修改,导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。undolog版本链记录这条记录的所有历史版本,版本之间通过隐式字段回滚指针进行关联。
  • readView

    • ReadView读视图是快照读sql执行时MVCC读取数据的依据,记录并维护当前活跃的(未提交)事务id。

      • m_ids:当前活跃事务id集合。
      • min_trx_id:最小活跃事务id。
      • max_trx_id:预分配事务id,当前最大事务id+1。
      • creator_trx_id:readView创建者的事务id。
    • 版本链数据访问规则:

      image.png

    • 不同隔离级别,生成ReadView的时机不同:

      • 读已提交RC:在事务中每一次执行快照读时生成ReadView。
      • 可重复读RR:仅在事务的第一次执行快照读时生成ReadView,后续复用该ReadView。

MVCC实现RR不可重复读

在RC读已提交下, 在事务中每一次执行快照读时生成ReadView, 这也就造成了每次读取就有不同ReadView, 那么就会读到已提交的事务修改的内容, 造成不可重复读的问题。

在隔离级别为可重复读RR时, 仅在事务中第一次执行快照读时生成ReadView, 后续复用该ReadView.由于后续复用了ReadView, 所以数据对当前事务的可见性和第一次是一样的, 所以从undolog中读到的数据快照和第一次是一样的, 即便过程中有其他事务修改也读不到.