InnoDB下的事务

事务的特性

原子性:

整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态。

一致性:

事务执行前与执行后都必须始终保持系统处于一致的状态。

隔离性:

并发事务之间不会相互干扰,彼此独立执行。

持久性:

在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中。

并发事务带来的问题

更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了其他事务所做的更新。例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改保存其更改副本的编辑人员覆盖另一个编辑人员所做的修改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题。

脏读(Dirty Reads):事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。

不可重复读(Non-Repeatable Reads):事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。不可重复读侧重于修改。解决不可重复读的问题只需锁住满足条件的行。

幻读(Phantom Reads):事务A将某一字段值修改为1234,事务B将同一字段值修改为abcd,A提交事务后发现存在abcd的数据。幻读侧重于新增或删除。解决幻读需要锁表。

事务隔离级别

隔离级别

脏读

不可重复读

幻读

读未提交

允许

允许

允许

读已提交

不允许

允许

允许

可重复读

不允许

不允许

允许

串行化

不允许

不允许

不允许

mysql默认隔离级别是可重复读。

事务隔离级别为读提交时,写数据只会锁住相应的行。

事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。

事务隔离级别为串行化时,读写数据都会锁住整张表。

实现事务的三大工具

实现事务需要三个工具, 日志文件、锁机制、MVCC。

事务要想解决处理前和处理后一致性,就需要有标识记录修改前和修改后的状态,mysql针对修改前和修改后提供了两个日志文件 -- undo log和redo log。

当多个并发请求过来,并且其中有一个请求是对数据修改操作的时候会有影响,为了避免读到脏数据,所以需要对事务之间的读写进行隔离,至于隔离到啥程度得看业务系统的场景了,实现这个就得用MySQL 的隔离级别。

MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列, 一个保存了行的创建时间,一个保存了行的过期时间, 当然存储的并不是实际的时间值,而是系统版本号。目的是做到读写分离。

redo log

redo log叫做重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中。假设有个表叫做tb1(id,username) 现在要插入数据(3,ceshi)

[图片上传失败...(image-9973c4-1581669588999)]

start transaction;

select balance from bank where name="zhangsan";

// 生成 重做日志 balance=600

update bank set balance = balance - 400;

// 生成 重做日志 amount=400

update finance set amount = amount + 400;

commit;

redo log 作用

mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。

那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?还没来得及执行上面图中红色的操作。这样会导致丢部分已提交事务的修改信息!

所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。

总结:

redo log是用来恢复数据的 用于保障已提交事务的持久化特性。

undo log

undo log 叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。

每次写入数据或者修改数据之前都会把修改前的信息记录到 undo log。

undo log 作用

undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。

总结:

undo log是用来回滚数据的用于保障 未提交事务的原子性。

mysql表锁

表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)

对于共享读锁,也就是读操作时锁可以共享,不会造成阻塞。写操作时会阻塞。

对于写锁,会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。读写都阻塞。

事务的隔离级别就是通过锁原理实现的。

原子性的实现

使用 undo log ,从而达到回滚。 当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,所以这时候就需要用到undo log来进行回滚。

执行一条update语句的步骤

1、开始事务

2、查询数据

3、进行update操作

4、产生被修改前数据的回滚日志,存到undo log中

5、事务提交/回滚

为了做到同时成功或者失败,当系统发生错误或者执行rollback操作时需要根据undo log 进行回滚:

(1) 如果在回滚日志里有新增数据记录,则生成删除该条的语句

(2) 如果在回滚日志里有删除数据记录,则生成生成该条的语句

(3) 如果在回滚日志里有修改数据记录,则生成修改到原先数据的语句

一致性的实现

通过回滚,以及恢复,和在并发环境下的隔离做到一致性。

隔离性的实现

使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行。隔离性是要保证能够按照一定的顺序执行。

读未提交

读未提交造成脏读的原因是因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。

读未提交很少用到实际的开发场景中,因为对并发的控制太低。

读提交

InnoDB在 READ COMMITTED,使用排它锁,读取数据不加锁而是使用了MVCC机制。或者换句话说他采用了读写分离机制。但是该级别会产生不可重读以及幻读问题。

这跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。

提交读这种隔离级别保证了读到的任何数据都是提交的数据,避免读到中间的未提交的数据,脏读(dirty reads)。但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据。

在实际场景中,基本都使用这个级别。

重复读

在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC。

采用读写锁实现:

只要没释放读锁,再次读的时候还是可以读到第一次读的数据。

采用MVCC实现:

因为多次读取只生成一个版本,读到的自然是相同数据。

串行化:

采用写锁

持久性的实现

使用 redo log,从而达到故障后恢复。事务一旦提交,其所作做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。

数据如果直接存储在磁盘上,每次读写都会非常消耗性能。为了降低性能,InnoDB提供了缓冲池,Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用。

读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;

写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;

和原子性的操作一致,只是第4步,产生被修改后数据的重做日志,存到redo log中

执行一条update语句的步骤

1、开始事务

2、查询数据

3、进行update操作

4、产生被修改后数据的重做日志,存到redo log中

5、事务提交/回滚

6、提交到磁盘

文章引用