Mysql事务篇

一、insert语句执行流程

  Mysql事务篇

二、事务介绍

  数据库具有ACID四大特性:

    原子性(atomicity):事务最小工作单元,要么全成功,要么全失败。

    一致性(consistency):事务开始和结束之后,数据库完整性不会被破坏

    隔离性(isolation):不同事务间互不影响,四种隔离级别RU(读未提交)、RC(读已提交)、RR(可重复读)、Serializable(串行化)

    持久性(durability):事务提交后,对数据额修改是永久性的

  隔离级别:

    读未提交:可以读到其他事务未提交的数据,会发生脏读

    读已提交:可以读取到其他事务已提交的数据,会发生不可重复读

    可重复读:在一个事务中,多次读取数据保持一致,无论其他事务是否对查询数据做了修改,无论修改的事务是否提交。

    串行化:以上三种隔离级别都允许对同一条记录进行读读、读写、写读的并发操作,串行化不允许读写、写读的并发操作。

  查询事务隔离级别

mysql> show global variables like '%isolation%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation          | REPEATABLE-READ |
+-----------------------+-----------------+

  设置全局隔离级别

set global transaction_isolation='READ-UNCOMMITTED';

  设置当前会话事务隔离级别

set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;   

三、事务和MVCC底层原理详解

(一)LBCC&MVCC  

  先来说一个场景,a=10,如果事务1将查询a为10,事务2查询a也为10,然后事务1将a更新为20,事务提交,然后事务2撤销,那么此时,a仍然会重新变为10,而非20,这就出现了数据的不一致,那么如何解决呢?

  解决方案一:LBCC

    使用LBCC(基于锁的并发控制)可以解决上述的问题,就是在事务1查询时,就加行锁,此时,事务2对a做变更时,就会被阻塞。

    但是这种方案比较简单粗暴,就是一个事务去读数据的时候,就上锁,不允许其他事务来操作。

    Mysql加锁后就是当前读,假如当前事务只是共享锁,那么其他事务就不能有排他锁,也就是不能修改数据,而加入当前事务需要加排他锁,那么其他事务就不能加任何锁。总之,能加锁成功,就确保了除当前事务外,其他事务不会对当前数据产生影响,所以,当前事务读取到的数据就只能是最新的,而不会是快照数据。

  解决方案二:MVCC

    使用MVCC(多版本并发控制)可以解决上述问题。

    MVCC使得数据库不会对数据加锁,普通的查询请求不会加锁,提高了数据库的并发处理能力,借助MVCC,数据库可以实现读已提交和不可重复读等隔离级别,用户可以查看当前数据的前一个或多个版本,保证了ACID的特性(隔离性)。

(二)InnoDB中的MVCC

  通过上面所说,LBCC是基于锁的并发控制,而MVCC是基于数据库提供并发访问控制的并发控制技术;其中MVCC最大的好处就是:读不加锁、读写不冲突。

  多版本并发控制仅仅只是一种技术概念,其核心理念就是数据快照,不同的事务访问不同版本的数据快照,从而实现不同的事务隔离级别。虽然说字面上说具有多个版本的数据快照,但是实际上并没有拷贝数据,因为拷贝数据会非常占用存储空间,InnoDB则是通过事务的undo日志巧妙的实现了多版本的数据快照。

  数据库的事务有时需要进行回滚操作,这是就需要对之前的操作进行undo,因此,在对数据进行修改时,InnoDB会产生undo log,当事务需要进行回滚时,InnoDB可以利用这些undo log将数据回滚到修改之前的样子。

  1、当前读和快照读

    在MVCC并发控制中,读操作可以分为当前读(current read)和快照读(snapshot read)。当前读读取的是记录里最新的版本,并且当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录;快照读读取的是记录的可见版本,有可能是历史版本,不用加锁。

    在InnoDB存储引擎中,简单的select操作,属于快照读,不加锁;对于增删改操作,属于当前读,需要加锁,加行写锁,读当前版本。

  2、一致性非锁定读

    一致性非锁定读是指InnoDB存储引擎通过多版本控制读取当前数据库中行数据的方式,如果读取的行正在执行删除或更新操作,这时读取操作不会因此去等待行锁的释放,而是去读一个最新可见快照。

    Mysql事务篇

 

 

   3、回滚段undo log

    为了更好的支持并发,InnoDB的MVCC采用了回滚段的方式,其对于更新和删除操作,InnoDB并不是真正的删除原来的记录,而是设置记录的删除标志为1,同时为了解决数据页和undo log膨胀的问题,引入了purge机制进行回收。undo log保存了记录修改前的镜像。

    在InnoDB存储引擎中,根据操作不同,undo log分为insert undo log 和 update undo log

    insert undo log:是在插入时产生的undo log,因为插入的记录只对事务本身可见,对于其他事务是不可见的,所以insert undo log可以在事务提交后直接删除,而不需要进行purge操作。

    update undo log:是update或者delete产生的undo log,因为会对已经存在的数据产生影响,为了提供MVCC机制,因此update undo log不能再事务提交时就进行删除,而是将食物提交时放入history list上,等待purge线程进行最后的删除操作。

    InnoDB行记录中有三个隐藏字段:行id(row_id)、事务号(db_trx_id)、回滚指针(db_roll_ptr),其中事务号表示最近修改的事务id,回滚指针指向回滚段中的undo log。同时对于InnoDB存储引擎来说,他的聚簇索引必须要包含事务号和回滚指针两个字段。每次对聚簇索引记录进行改动时,都会把对应的事务id赋值给版本号,把旧的事务版本号写入undo log中,然后将该undo log的指针写入回滚指针中。

    那么多次变更的数据,undo 日志就会是一个类似链表的结构,最新的数据在链表的头部:

    Mysql事务篇 

 

 

(三)ReadView

  对于事务的四种隔离级别来说,如果时读未提交,那么直接使用最新版本就可以了,如果是串行化,直接就使用了加锁的方式来进行控制了,而读已提交和不可重复读就用到了上图中的版本链。其核心问题就是哪个版本中的数据对当前事务是可见的,所以InnoDB设计了一个ReadView的概念,这个ReadView中主要包含当前系统中还有哪些活跃的读写事务,把他们的事务ID放到一个列表中(m_ids),这样,在访问某条记录时,只需要按照下述原则判断是否可见即可:

  从版本链的头部进行判断,如果符合可见条件,就返回,如果不符合,就继续获取前一个版本继续判断,如果到最早的一个版本还是不可见,则返回不可见。

    (1)如果被访问版本的事务id小于m_ids列表中最小的事务id,说明该事务在生成ReadView之前已经提交,所以该版本可被当前事务访问。

  (2)如果被访问版本的事务id大于m_ids列表中最大的事务id,说明该事务在生成ReadView之后才生成,所以该版本不可被当前事务访问。

  (3)如果被访问版本的事务id在m_ids最大值和最小值之间,那就需要判断事务id的值是否在m_ids中,如果在,就说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不存在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

  在Mysql中读已提交和不可重复读两个隔离级别一个非常大的区别就是他们生成ReadView的时机不同,读已提交是在每次读取都会生成一个ReadView,不可重复读是在第一次读取数据时会生成一个ReadView。

  从上面可以看到,MVCC主要是在使用读已提交和不可重复读这两种隔离级别下,普通的select查询访问版本链的过程,这样可以是不同事务的读写、写读操作并发执行,从而提高性能。

四、事务回滚和数据恢复

  事务的隔离性由多版本控制机制和锁实现,而原子性、持久性、一致性主要通过redo log、undo log、force log at commit机制来完成,其中redo log用在崩溃时恢复数据,undo log用在对事务回滚或用于多版本控制,force log at commit机制保证事务提交后redo log日志都已经持久化。

  其中redo log就是重做日志,每次数据的sql操作导致的数据表话其都会进行记录,具体地说,redo log是物理日志,记录的是数据页的物理修改操作。如果数据丢失,可以使用其进行恢复。

  InnoDB通过force log at commit机制来实现事务的持久性,即当食物提交时,必须先将事务的所有日志都写到redo log文件并持久化后,commit才算成功。

  当事务的各种sql操作执行时,会在缓冲区中修改数据,也会将对应的redo log写入其对应的缓冲区,当事务执行提交时,与该事务相关的redo log缓冲碧血全部刷新到磁盘才算提交完成。

  数据库日志和数据罗盘机制如下图所示:

  Mysql事务篇

 

   当事务执行过程中,除了记录redo log之外,还会记录一定量的undo log,undo log记录了数据在每个操作前的状态,如果事务执行过程中需要进行回滚,就可以根据undo log进行回滚操作。

  数据库事务的整个流程如下:

  Mysql事务篇

 

   事务进行过程中,每次DML语句执行,都会记录undo log和redo log,然后更新数据形成脏页,然后redo log按照时间或者空间等条件进行落盘,undo log和脏页按照checkpoint进行落盘,罗盘后对应的redo log就可以删除了,此时事务还未提交,如果数据库崩溃,则首先检查checkpoint记录,使用相应的redo log进行数据和undo log的恢复,然后检查undo log的状态发现事务还未提交,然后就使用undo log进行事务回滚。如果事务提交,则会将本事务相关的redo log都进行落盘,只有所有redo log都落盘,才算事务提交成功。然后内存中的脏页继续按照checkpoint进行落盘,如果此事发生了崩溃,则只使用redo log恢复数据即可。

上一篇:mysql系统日志 (binlog, redolog, undolog, errorlog, generallog, relaylog, slowquerylog)


下一篇:Oracle-DG最大保护模式下,dg备库出现问题对主库有什么影响?