Mysql基本原理:事务、隔离、锁

本文详细介绍了MySQL的事务基础概念,包括事务的ACID特性、事务的实现技术如redo log、undo log和锁机制。还探讨了事务的隔离性,包括四种隔离级别及其可能导致的问题,并分析了如何通过MVCC解决并发问题。内容覆盖了事务的原子性、持久性、隔离性和一致性,是理解MySQL事务原理的重要参考资料。
摘要由CSDN通过智能技术生成

MySQL基本原理:事务、隔离、锁


参考资料:
https://www.cnblogs.com/kismetv/p/10331633.html
https://www.cnblogs.com/wyc1994666/p/11367051.html

一、基础概念

1.1 事务简介

作为一个关系型数据库,MySQL支持事务(Transaction)

事务是MySQL等关系型数据库区别于NoSQL的重要方面,是保证数据一致性的重要手段。

(1)事务是访问和更新数据库的程序执行单元;

(2)在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务;

(3)事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行;

(4)事务用来管理 insert、update、delete 语句;

1.2 逻辑架构和存储引擎

img

MySQL服务器逻辑架构从上往下分为三层:

(1)处理客户端连接、授权认证等;

(2)服务器层,负责查询语句的解析、优化、缓存以及内置函数的实现、存储过程等;

(3)存储引擎,负责MySQL中数据的存储和提取。**MySQL中服务器层部管理事务,事务是由存储引擎实现的。**MySQL支持事务的存储引擎由InnoDB、NDB Cluster等,其中InnoDB的使用最为广泛;其他存储引擎不支持事务,如MyISAM、Memory等;

1.3 事务要达到什么效果

(1)可靠性:数据库要保证当insert或update操作时抛异常或者数据库crash的时候需要保障数据的操作前后的一致,想要做到这个,需要指导修改前后的状态,所以由了undo logredo log

(2)并发处理:当多个并发请求过来,并且其中有一个请求是对数据修改操作的时候会有影响,为了避免读到脏数据,所以需要对事务之间的读写进行隔离,至于隔离到什么程度取决于业务场景,MySQL隔离级别就是 用来实现该效果的。

1.4 提交和回滚

(1)典型的MySQL事务是如下操作的:

start transaction;
…… #一条或多条sql语句
commit;

其中start transaction标识事务的开始,commit提交事务,将执行结果写入到数据库;

如果sql语句执行踹性能问题,会调用rollback回滚所有已执行成功的sql语句,也可以再事务中直接使用rollback进行回滚。

(2)自动提交:

MySQL中默认采用的是自动提交(autocommit)模式,在自动提交模式下,如果没有start transaction显式地开始事务,那么每个sql语句都会被当作一个事务执行提交操作;
在这里插入图片描述

如果关闭了自动提交,则所有的sql语句都在一个事务中,直到执行了commit或者rollback,该事务结束,同时开始了另一个事务,可以通过set autocommit = 0;来关闭。

(3)特殊操作

在MySQL中,存在一些特殊命令,如果在事务中执行了这些命令,会马上强制执行commit提交事务:

如DDL语句(creat table/drop table/alter table)、lock tables语句

常用的select、insert、update、delete等不会强制提交事务。

1.5 事务四大特性:ACID特性

注意:按照严格的标准,只有同时满足ACID特性才是事务;但是在各大数据库厂商的实现中,真正满足ACID的事务少之又少。例如MySQL的NDB Cluster事务不满足持久性和隔离性;InnoDB默认事务隔离级别是可重复读,不满足隔离性;Oracle默认的事务隔离级别为READ COMMITTED,不满足隔离性……因此与其说ACID是事务必须满足的条件,不如说它们是衡量事务的四个维度。

(1)原子性(Atomicity,或称为不可分割性):

原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做;如果事务中一个sql语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态;

(2)一致性(Consistency):

一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态,这表示写入的资料必须完全符合所有的预设规则;数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。

(3)隔离性(Isolation):

隔离性研究的是不同事物之间的相互影响,隔离性是指事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能相互干扰,防止多个事务并发执行时由于交叉执行而导致数据不一致;

事务隔离分为不同级别:未提交读(read uncommitted)、提交读(read committed)、可重复读(repeatable read)、串行化(serializable);

严格的隔离性对应了可串行化,但实际应用中处于性能方面考虑很少使用可串行化(银行卡业务场景下会使用,比如在从银行卡中取钱的过程中不能向该银行卡中打钱);

隔离性追求的是并发情境下事务间互不干扰,简单起见主要考虑读操作和写操作,则隔离性的讨论可以分为两个方面:

a.一个事务写操作对另一个事务写操作的影响:锁机制保证隔离性;

b.一个事务写操作对另一个事务读操作的影响:MVCC保证隔离性。

(4)持久性(Durability):

持久性指事务一旦提交,它对数据库的改变就应该是永久性的,接下来的其他操作或古战更不应该对其有任何影响。

1.6 实现事务功能的三个技术

(1)日志文件(redo log和undo log);

(2)锁技术;

(3)MVCC;

事务特性实现技术
原子性undo log
持久性redo log
隔离性锁机制+MVCC
一致性原子性+持久性+隔离性

ACID只是个概念,事务最终目的是要保障数据的可靠性,一致性。

二、实现技术

2.1 redo log 与 undo log

MySQL的事务日志:MySQL的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等;

此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。

1. redo log

(1)概念

redo log 叫做重做日志,用来实现事务的持久性。日志文件由两部分组成:重做日志缓冲(redo log buffer,在内存中)以及重做日志文件(redo log,在磁盘中)。当事务提交之后会把所有修改信息存到该日志中,如下示例:

假设有表tb1(id, username),现插入数据(3, ceshi)

img

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;

img

img

  • 第一步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝。
  • 第二步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值。
  • 第三步:在必要的时候,采用追加写的方式将 redo log buffer 中的内容刷新到 redo log file。
  • 第四步:定期将内存中修改的数据刷新到磁盘中。

(2)作用

InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为了提升性能,InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏,是使用后台线程去做缓冲池和磁盘之间的同步)。

在这种模式下,如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘(红线部分未完成),就会导致数据的丢失,事务的持久性无法保证。

因此引入redo log来记录已成功提交事务的修改信息,并将redo log持久化到磁盘,系统重启之后再读取redo log恢复最新数据。具体地,当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。

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

思考:

Q1.既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?

  • 1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。

  • 2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。

Q2.redo log与binlog(二进制日志)的不同?

  • 1)作用不同:redo log是用于crash recovery的,保证MySQL宕机也不会影响持久性;binlog是用于point-in-time recovery的,保证服务器可以基于时间点恢复数据,此外binlog还用于主从复制。
  • 2)层次不同:redo log是InnoDB存储引擎实现的,而binlog是MySQL的服务器层(可以参考文章前面对MySQL逻辑架构的介绍)实现的,同时支持InnoDB和其他存储引擎。
  • 3)内容不同:redo log是物理日志,内容基于磁盘的Page;binlog的内容是二进制的,根据binlog_format参数的不同,可能基于sql语句、基于数据本身或者二者的混合。
  • 4)写入时机不同:binlog在事务提交时写入;redo log的写入时机相对多元。
2. undo log

(1)概念

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

img

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

(2)作用

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

undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。

以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。

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

img

2.2 MySQL锁技术以及MVCC基础

1. MySQL锁技术

当有多个请求读取表中的数据时可以不采取操作;但是多个请求中既有读又有写(修改)请求时必须进行并发控制,否则会造成不一致。

(1)读写锁

用锁来对读写请求进行控制:

共享锁(shared lock),又称为读锁:读锁是可以共享的,多个读请求可以共享一把锁读数据,不会阻塞;

排他锁(exclusive lock),又称为写锁:写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。

读锁写锁
读锁可并行不可并行
写锁不可并行不可并行

事务的隔离性就是通过读写锁实现的。

2. MVCC基础

MVCC(MultiVersion Concurrency Control)多版本并发控制。

“InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号。” —— 《高性能MySQL》

MVCC主要实现思想是通过数据多版本来做到读写分离,从而实现不加锁读进而做到读写并行。

MVCC在MySQL中的实现依赖的是undo log 与 read view

  • undo log:undo log 中记录某行数据的多个版本的数据
  • read view: 用来判断当前版本数据的可见性

img

三、事务的实现

3.1 原子性的实现 —— undo log

“一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。” ——《高性能MySQL》

数据库通过回滚操作实现上述目的。

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

1. undo log 生成

img

img

从上图可以了解到数据的变更都伴随着回滚日志的产生:
(1) 产生了被修改前数据(zhangsan,1000) 的回滚日志

(2) 产生了被修改前数据(zhangsan,0) 的回滚日志

根据上面流程可以得出如下结论:
1.每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上
2.所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。

思考:为什么先写日志后写数据库?

2. 根据undo log 回滚

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

img

回滚操作就是要还原到原来的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,比如:

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

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

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

3.2 持久性的实现 —— redo log

在介绍redo log的时候已经讲的很较详细了

事务一旦提交,其所作做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。

先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘IO,然而即使是使用SSD磁盘IO也是非常消耗性能的。
为此,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:
读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;

上面这种缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!

因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。

于是 redo log就派上用场了。下面看下redo log是什么时候产生的

img

既然redo log也需要存储,也涉及磁盘IO为啥还用它?

(1)redo log 的存储是顺序存储,而缓存同步是随机操作。

(2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。

3.3 隔离性的实现

隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。

Mysql 隔离级别有以下四种(级别由低到高):

  • READ UNCOMMITED (未提交读)
  • READ COMMITED (提交读)
  • REPEATABLE READ (可重复读)
  • SERIALIZABLE (串行化)

img

前面说过原子性,隔离性,持久性的目的都是为了要做到一致性,但隔离型跟其他两个有所区别

  • 原子性和持久性是为了要实现数据的可性保障靠,比如要做到宕机后的恢复,以及错误后的回滚;

  • 隔离性是要管理多个并发读写请求的访问顺序。 这种顺序包括串行或者是并行;写请求不仅仅是指insert操作,又包括update操作。

img

从隔离性的实现可以看出这是一场数据的可靠性与性能之间的权衡。

  • 可靠性性高的,并发性能低(比如 Serializable)
  • 可靠性低的,并发性能高(比如 Read Uncommited)
1. READ UNCOMMITTED 未提交读

在READ UNCOMMITTED隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。

因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行

img

优点:读写并行,性能高;

缺点:脏读。

2. READ COMMITTED 提交读

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

不可重读:在一个事务内多次读取的结果不一样;

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

在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读
img

3. REPEATABLE READ 可重复读(MySQL默认隔离级别)

在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。

MySQL 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC

(1)采用读写锁实现

img
为什么能可重复度?只要没释放读锁,在第二次读的时候还是可以读到第一次读的数据。

优点:实现起来简单

缺点:无法做到读写并行

(2)采用MVCC实现
img

为什么能可重复度?因为多次读取只生成一个版本,读到的自然是相同数据。

优点:读写并行

缺点:实现的复杂度高

在该隔离级别下仍会存在幻读的问题。

幻读与不可重复度都是多次读取数据不一致,区别在于:

  • 不可重复读是针对修改update操作的;

  • 幻读是针对增加insert和删除delete操作的;

因此,解决不可重复读问题只需要采用行级锁,解决幻读需要采用表级锁。

4. SERIALIZABLE 串行化

该隔离级别理解起来最简单,实现也最简单。在隔离级别下除了不会造成数据不一致问题,没其他优点。

img

3.4 一致性的实现

下面举个例子:zhangsan 从银行卡转400到理财账户

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;

(1)假如执行完 update bank set balance = balance - 400;之发生异常了,银行卡的钱也不能平白无辜的减少,而是回滚到最初状态——原子性

(2)又或者事务提交之后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化——持久性;

(3)假如有并发事务请求的时候也应该做好事务之间的可见性问题,避免造成脏读,不可重复读,幻读等。在涉及并发的情况下往往在性能和一致性之间做平衡,做一定的取舍,所以隔离性也是对一致性的一种破坏——隔离性

3.5 总结

实现事务采取了哪些技术以及思想?

  • 原子性:使用 undo log ,从而达到回滚;
  • 持久性:使用 redo log,从而达到故障后恢复
  • 隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行
  • 一致性:通过回滚,以及恢复,和在并发环境下的隔离做到一致性。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值