MySQL学习(六):Innodb存储引擎的事务实现机制

概述

  • 事务机制主要用于实现数据库对并发访问的支持和在并发访问下的数据一致性和可靠性。MySQL的myisam存储引擎是不支持事务的,通过表锁来实现数据的可靠性,类似于Java多线程的同步锁synchronized,避免并发修改,但是并发性能较差,比较适合OLAP的应用,同时数据库奔溃恢复比较麻烦。
  • innodb存储引擎是支持事务的,支持多个客户端高性能地进行并发操作,相对myisam并发性能较好,比较适合OLTP的应用。innodb存储引擎实现了事务的ACID特性,即原子性,一致性,隔离性和持久性。
  • ACID特性主要用于保证并发访问时,数据的一致性和可靠性,是对简单地进行加表锁实现串行化访问来保证数据一致性和可靠性的一种改进和优化,实现了多个客户端的并发访问。

ACID实现

SQL标准的ACID的定义:

  • A:原子性,一次数据库访问包含的多个数据库操作是一个整体,要么全部成功执行,要么全部失败回滚,相当于一个原子操作,不允许部分成功,部分失败;

  • C:一致性,事务执行前后,数据保持一致状态,不存在数据不一致问题;

  • I:隔离性,或者称为并发性,实现多个客户端的并发事务操作相互隔离,互不影响。不过这个在数据库实现当中,影响程度一般需要和并发性取一个折中,即定义了多个隔离级别,每个隔离级别的隔离程度和并发性存在差异,具体后面分析。

  • D:只要事务成功提交,则数据保证持久化保存,即使数据库奔溃也能恢复回来,保证数据的可靠性。

Innodb的ACID实现

innodb存储引擎的原子性A和持久性D主要是通过redo重做日志来实现的,数据一致性除了redo日志外,需要undo日志来辅助实现,即当事务提交失败时,通过undo日志来实现回滚,取消该事务对数据库的操作。隔离性I主要是通过锁和MVCC机制来实现。

redo日志:原子性、持久性
  • redo日志主要是记录对数据的物理修改,在事务提交时,必须先将本次事务的所有修改写到redo日志中进行持久化,实现了原子性和持久性。redo日志写入成功之后才进行实际的事务提交,对数据库的数据进行实际的修改。
  • 所以redo日志记录了所有的数据修改操作,如果在事务提交时写入redo日志成功,再进行数据库修改时,数据库发送了崩溃,则可以在重启时,通过redo日志来恢复这些修改。如果事务提交之前,数据库奔溃,则该次事务操作的所有修改都没有执行,保证了原子性。
undo日志:一致性
  • 相对于redo日志记录数据的物理修改,undo日志是逻辑记录,是MySQL数据库内部的特殊的segment,segment内部对应每个事务都有一条记录,这个记录记录了该事务所有修改操作的逆过程。
  • 在事务操作过程中,每个修改操作都会记录在undo日志中,记录到undo日志的内容与该次修改操作相反,如INSERT对应DELETE,UPDATE则对应更新前的记录(这里记录了该数据的数据快照,负责实现了MVCC机制)。
  • 所以当该事务需要回滚时,只需要执行该事务对应的undo日志中的记录,即逆向的SQL语句,如插入了10条记录,则删除这10条记录。
  • 对于undo日志的删除,不是事务提交后就马上删除的,而是通过purge线程来完成的。由于MVCC机制是基于undo日志来实现,故该事务对应的数据行的快照数据可能被其他事务在引用,所以通过purge线程来检测是否存在其他事务在引用该数据行的快照,如果没有才进行该事务对应的undo日志的删除。
锁:隔离性
  • 事务的隔离性主要是针对写操作,因为读操作是不会对数据进行修改,故不存在隔离的说法,通过隔离多个对相同的数据进行并发修改的事务,保证数据的一致性。
  • 对于数据的修改操作一般需要加互斥锁(记为X锁),如果一个事务对数据进行了加X锁,则其他需要修改该数据的事务就需要阻塞等待该事务释放该X锁才能继续执行,所以加锁操作直接影响了并发性能。基于并发性和数据一致性的考虑,隔离性定义了READ-UNCOMMITTED,READ-COMMITTED,REPEATABLE-READ, SERIALIZABLE这四个隔离级别,并发性能依次变差,数据一致性依次变强。

事务的隔离:并发性

  • 以上分析了事务的隔离性主要是通过锁和MVCC机制来实现,而隔离性主要是解决并发事务操作存在的以下问题,并通过事务隔离机制来规避这些问题,不过不是所有的隔离级别都会解决所有的这些问题,因为还需要与并发性取一个折中。

事务存在的问题

脏读
  • 在一个事务内部读取到了其他未提交事务的数据修改,如果基于这些未提交的数据做计算或者更新,则如果发生事务回滚或其他事务之后又进行了修改,则会出现数据不一致问题。
幻读
  • 在一个事务内部,两次读取的数据行数不一样,如其他事务进行数据插入或者数据删除操作。
不可重复读
  • 在整个事务期间,相同操作的多次执行都是返回相同的数据结果集,包括数据行内容和数据行数量。
更新丢失
  • 数据覆盖:一个事务覆盖了另外一个事务的执行结果;
  • 数据脏读:一个事务基于另外一个未提交事务的数据修改来做修改,当另外一个事务回滚后,当前事务基于该脏数据进行修改导致数据不一致。

隔离级别的定义与Innodb的实现

为了解决以上并发事务操作存在的问题和与并发性取个折中,定义了以下四个隔离级别:

READ-UNCOMMITTED:读未提交
  • 没有解决以上的任何一个问题,读写均不需要加锁,数据一致性最差,并发性能最好,存在并发操作的应用,如OLTP,基本不会使用这个隔离级别;
READ-COMMITTED:读提交
  • 能够读取其他已提交事务的数据修改,不存在脏读和更新丢失问题,存在幻读和不可重复读问题。
REPEATABLE-READ:可重复读
  • 解决了以上的所有问题,即不存在脏读,幻读,不可重复读和更新丢失问题,这也是MySQL的innodb存储引擎的默认隔离级别。

  • 不可重复读的解决:读提交和可重复读的读操作都是基于MVCC机制来实现一致性无锁读取,区别在于可重复读是基于事务开始时的数据快照,整个事务期间不变,故多次读取数据一致;而读提交是基于数据的最新快照,即其他已提交事务的最新修改快照,故事务期间的两次读取可能不一样。

  • 幻读的解决:读提交和可重复读的写操作都是基于互斥行锁实现,但是二者使用的锁的算法存在差别,读提交只锁住匹配的行,可重复读锁住匹配的行和周围的行(间隙锁的存在),即可重复读需要锁住更多的数据行,其他事务不能对这些被锁住的行进行插入、删除操作,故不会新增或减少数据行,不存在幻读发生。具体可参考::MySQL学习(五):Innodb存储引擎锁与MVCC机制的实现原理

  • REPEATABLE-READ,相对于SERIALIZABLE串行化,数据一致性方面唯一的不足是基于MVCC机制读取的数据的快照版本,故数据可能存在延迟。

SERIALIZABLE:串行化
  • 解决了以上的所有问题,对读写操作均需要加锁,读操作加共享锁,写操作加互斥锁,串行化了多个事务操作,并发性能最差,数据一致性最好。
    原文作者:mysql
    原文地址: https://blog.csdn.net/u010013573/article/details/89072087
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞