MySQL数据库事务

2021/5/23 2:28:04

本文主要是介绍MySQL数据库事务,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

转载:MySQL 三万字精华总结 + 面试100 问,和面试官扯皮绰绰有余(收藏系列) (juejin.cn)

1 ACID事务基本要素

一个事物由一批操作组合而成,要么全部成功,要么全部失败。

事务四要素:

  • A (Atomicity) 原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • C (Consistency) 一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
  • I (Isolation)隔离性:一个事务的执行不能被其它事务干扰。也就是说一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。(这里的意思并不是说并发事务不会出现共享资源的一致性问题,还是有的)。
  • D (Durability) 持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

2 并发事务引发的问题

(1)并发事务会引起下面的问题:

  • 更新丢失(Lost Update): 事务A和事务B选择同一行,然后基于最初选定的值更新该行时,由于两个事务都不知道彼此的存在,就会发生丢失更新的问题。

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

  • 不可重复读(Non-Repeatable Reads):事务 A 多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

  • 幻读(Phantom Reads):幻读与不可重复读类似。它发生在一个事务A读取了几行数据,接着另一个并发事务B插入了一些数据时。在随后的查询中,事务A就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。


(2)不可重复读幻读 的区别:

  • 不可重复读的重点是修改同一条数据:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改,导致这条数据变了)。
  • 幻读的重点在于新增或者删除:在同一事务中,同样的条件,第一次和第二次读出来的记录数不一样(多了或者少了)。(因为中间有其他事务提交了插入/删除数据的操作)。

(3)并发事务问题的解决办法:

  • “更新丢失” 通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据 加必要的锁 来解决,因此,防止更新丢失应该是应用的责任。
  • “脏读” 、 “不可重复读”、“幻读” ,其实都是数据库读一致性问题,必须由数据库提供一定的 事务隔离机制 来解决:
    • 一种是加锁:在读取数据前,对其加锁,阻止其他事务对数据进行修改。(效率低
    • 另一种是数据多版本并发控制(MultiVersion Concurrency Control,简称 MVCC 或 MCC),也称为多版本数据库:不用加任何锁, 通过一定机制生成一个数据请求时间点的一致性数据 快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本。

3 事务隔离级别

数据库事务的隔离级别有4种,由低到高分别为:

  • READ-UNCOMMITTED(读未提交): 最低的隔离级别,允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复读
  • READ-COMMITTED(读已提交): 允许读取并发事务已经提交的数据,就是一个事务要等另一个事务提交后才能读取数据。可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • REPEATABLE-READ(可重复读): 就是在开始读取数据(事务开启)时,不再允许修改操作,对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改。可以阻止脏读和不可重复读,但幻读仍有可能发生MySQL的默认事务隔离级别
  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个串行执行,这样事务之间就完全不可能产生干扰。该级别可以防止脏读、不可重复读以及幻读

数据库的事务隔离越严格,并发副作用越小,但付出的代价就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化” 进行,这显然与 “并发” 是矛盾的。

MySQL的默认事务隔离级别是:REPEATABLE-READ

这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**已经可以完全保证事务的隔离性要求,即达到了 SQL标准的 SERIALIZABLE(可串行化) 隔离级别,而且保留了比较好的并发性能。

4 MVCC 多版本并发控制

MySQL的大多数事务型存储引擎实现都不是简单的行级锁。基于提升并发性考虑,一般都同时实现了多版本并发控制(MVCC)。

可以认为 MVCC 是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。 虽然实现机制有所不同,但大都 实现了非阻塞的读操作,写操作也只是锁定必要的行

MVCC 的实现是通过保存数据在某个时间点的快照来实现的。也就是说不管需要执行多长时间,每个事物看到的数据都是一致的。

典型的MVCC实现方式,分为 乐观(optimistic)并发控制悲观(pressimistic)并发控制

InnoDB 的 MVCC,是通过在每行记录后面保存 两个隐藏的列 来实现。这两个列,一个保存了行的创建时间,一个保存 行的过期时间(删除时间)。当然存储的并不是真实的时间,而是 系统版本号(system version number)。每开始一个新的事务,系统版本号都会 自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。

REPEATABLE READ(可重读)隔离级别下MVCC如何工作:

  1. SELECT 操作

    InnoDB 会根据以下两个条件检查每行记录:

    • InnoDB 只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在开始事务之前已经存在要么是事务自身插入或者修改过的。
    • 行的删除版本号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行在事务开始之前未被删除。

只有符合上述两个条件的才会被查询出来。

  1. INSERT 操作

    InnoDB 为新插入的每一行保存当前系统版本号作为行版本号。

  2. DELETE 操作

    InnoDB 为删除的每一行保存当前系统版本号作为行删除标识。

  3. UPDATE 操作

    InnoDB 为插入的一行新纪录保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识。

注意:

  • 保存这两个额外系统版本号,使大多数操作都不用加锁。使数据操作简单,性能很好,并且也能保证只会读取到符合要求的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作和一些额外的维护工作。

  • MVCC 只在 COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作。

5 事务日志

事务的隔离性(I)是通过实现;而事务的原子性(A)、一致性(C)和持久性(D)则是通过事务日志实现 。

InnoDB 使用日志来减少提交事务时的开销。因为日志中已经记录了事务,就无须在每个事务提交时把缓冲池的脏块刷新(flush)到磁盘中。

一旦日志安全写到磁盘,事务就持久化了,即使断电了,InnoDB可以重放日志并且恢复已经提交的事务。

InnoDB 使用一个后台线程智能地刷新这些变更到数据文件。这个线程可以批量组合写入,使得数据写入更顺序,以提高效率。

事务日志可以帮助提高事务效率

  • 使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久化到磁盘。
  • 事务日志采用的是 追加 的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。
  • 事务日志持久以后,内存中被修改的数据在后台可以慢慢刷回到磁盘。
  • 如果数据的修改已经记录到事务日志并持久化,但数据本身没有写回到磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这一部分修改的数据。

目前来说,大多数存储引擎都是这样实现的,我们通常称之为 预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘(一次写日志,一次写数据)。

5.1 两种事务日志

事务日志包括:redo log(重做日志)和 undo log(回滚日志)

  • redo log(重做日志):实现持久性和原子性

    事务开启时,事务中的操作,都会先写入存储引擎的日志缓冲里,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化,这就是DBA们口中常说的 “预写式日志”(Write-Ahead Logging)。当事务提交之后,在Buffer Pool中映射的数据文件才会慢慢刷新到磁盘。此时如果数据库崩溃或者宕机,那么当系统重启进行恢复时,就可以根据 redo log 中记录的日志,把数据库恢复到崩溃前的一个状态。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定。

    在系统启动的时候,就已经为 redo log 分配了一块连续的存储空间,以顺序追加的方式记录redo Log,通过顺序IO来改善性能。所有的事务共享redo log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起。

  • undo log(回滚日志):实现一致性

    undo log 主要为事务的回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。

    undo记录的是已部分完成并且写入硬盘的未完成的事务,默认情况下回滚日志是记录下表空间中的(共享表空间或者独享表空间)。

二种日志均可以视为一种恢复操作,redo_log是恢复提交事务修改的页操作,而undo_log是回滚行记录到特定版本。二者记录的内容也不同,redo_log是物理日志,记录页的物理修改操作,而undo_log是逻辑日志,根据每行记录进行记录。

5.2 MySQL 有多少种日志?

  • 错误日志:记录出错信息,也记录一些警告信息或者正确的信息。
  • 查询日志:记录所有对数据库请求的信息,不论这些请求是否得到了正确的执行。
  • 慢查询日志:设置一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询的日志文件中。
  • 二进制日志:记录对数据库执行更改的所有操作。
  • 中继日志:中继日志也是二进制日志,用来给slave 库恢复。
  • 事务日志:重做日志redo和回滚日志undo。


这篇关于MySQL数据库事务的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程