MySQL事务:ACID实现原理实现数据一致性
事务是MySQL等关系型数据库区别于NoSQL的重要方面,也是保证数据一致性的重要手段。
MySQL博大精深,文章中的疏漏在所难免。欢迎批评和指正。
1。基本概念
事务(Transaction)是访问和更新数据库的程序执行单元;一个事务可以包含一个或多个 SQL 语句,这些语句要么全部执行,要么不执行。作为关系型数据库,MySQL 支持事务。本文基于MySQL5.6。
首先回顾一下MySQL事务的基础知识。
1。逻辑架构和存储机制
如上图所示,MySQL服务器的逻辑架构从上到下可以分为三层:
第一层:处理客户端连接、授权认证等。 。
第二层:服务器层负责解析、优化、缓存查询语句、实现内置函数、存储过程等。
第三层: 存储机制,负责在MySQL中存储和检索数据。 MySQL中的服务器层不管理事务,而是由存储引擎执行事务。支持事务的MySQL存储引擎有InnoDB、NDB Cluster等,其中InnoDB应用最为广泛;其他存储机制如MyIsam、Memory等不支持事务。
除非另有说明,以下文章中描述的内容均基于InnoDB。
2。提交和回滚
典型的 MySQL 事务工作原理如下:
start transaction;
…… #一条或多条sql语句
commit;复制代码
其中 transaction start 标识事务的开始,commit 提交事务并将执行结果写入数据库。如果执行SQL语句出现问题,则会调用回滚,回滚所有执行成功的SQL语句。当然,您也可以直接在交易中使用退款单进行退款。
自动提交
默认情况下,MySQL使用自动提交模式,如下所示:
如果自动提交模式下没有启动事务来显式启动事务,则每条sql语句都会被视为一个事务来执行提交操作。
可以通过以下方式关闭自动提交;应该注意的是,自动提交参数是特定于连接的。更改一个连接中的参数不会影响其他连接。
如果自动提交关闭,则所有 SQL 语句都在一个事务中,直到执行提交或回滚,该事务结束并开始另一事务。
特殊操作
MySQL中有一些特殊的命令。如果这些命令在事务中执行,commit将被强制立即提交事务;如DDL语句(create table/drop table/alter/table)、表锁定语句等。
但是,常用的选择、插入、更新和删除命令不会强制交易被批准。
3。 ACID 属性
ACID 是四种事务属性的度量:
- 原子性:(原子性)
- 一致性:(一致性)
- 隔离性:(隔离性)
- 持久性 持久性:(持久性)
符合严格的标准,只有满足ACID特性的事务才被视为事务;然而,在各大数据库厂商的实现中,真正符合ACID的事务却很少。例如MySQL NDB Cluster事务不符合持久性和隔离性;默认的InnoDB事务隔离级别是可重复读,与隔离不对应; Oracle默认的事务隔离级别是READ COMMITTED,它并不对应于隔离……所以与其说ACID是事务必须满足的条件,不如说有四个维度来衡量事务。下面将详细介绍
ACID的特点及其实现原理;为了便于理解,介绍的顺序并不严格为A-C-I-D。
2. 原子性
1.定义
原子性是指事务是一个不可分割的工作单元,其中执行所有操作或不执行任何操作;如果事务中的SQL语句没有执行,则执行的语句也必须回滚,数据库返回到事务前的状态。
2。实现原理:undo log
在解释原子性原理之前,先介绍一下MySQL事务日志。 MySQL日志有多种类型,如二进制日志、错误日志、查询日志、慢查询日志等。另外,InnoDB存储引擎还提供两种事务日志:重做日志(redo log)和撤消日志(rollback log)。重做日志用于保证事务的持久性;撤消日志是事务原子性和隔离性的基础。
我们来谈谈撤消日志。实现原子性的关键是能够在事务回滚时撤消所有成功执行的 SQL 语句。InnoDB依靠undo log来实现回滚:当事务修改数据库时,InnoDB会创建相应的undo log;如果事务失败或调用回滚,导致事务被回滚,则可以使用undo log中的信息将数据回滚到更改之前的状态。
undo log是逻辑日志,记录与SQL执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每一次插入,回滚时都会执行一次删除;对于每次删除,回滚时都会执行一次插入;对于每次更新,回滚期间都会执行擦除。移动时,会进行反向更新,将数据改回来。
以更新操作为例:当事务执行更新时,生成的undo log会包含更改行的主键(这样我们就知道更改了哪些行)、更改了哪些列、值更改之前和之后的那些列等。可在回滚期间使用的信息,将数据恢复到更新前的状态。
3。耐力
1。定义
持久性是指一旦提交事务,其更改必须在数据库中永久存在。以后的操作或故障不应影响它。
2。实现原理:redo log
Redo log和undo log都属于InnoDB事务日志。首先我们先来说一下重做日志存在的背景。
InnoDB是MySQL存储引擎,数据存储在磁盘上。但如果每次都需要磁盘IO来读写数据,那么效率会很低。为此,InnoDB提供了缓冲区(Buffer Pool)。 Buffer Pool包含一些数据页在磁盘上的映射,作为访问数据库的缓冲区:从数据库读取数据时,会首先从Buffer Pool中读取。如果缓冲池池中不存在的话,就会从磁盘读取并放入缓冲池中;当向数据库写入数据时,会首先写入缓冲池,缓冲池中发生变化的数据会定期刷新到磁盘上(这个过程称为脏刷新)。
缓冲的使用大大提高了读写数据的效率,但也带来了新的问题:如果MySQL宕机,缓冲区中修改的数据还没有刷新到磁盘,数据就会丢失。 ,无法保证交易的持久性。
所以,为了解决这个问题,引入了重做日志:当数据发生改变时,除了改变缓冲池中的数据外,操作也会记录在重做日志中;当事务提交时,会调用fsync接口进行redo log刷盘。如果MySQL宕机了,可以读取重做日志中的数据,并在数据库重启时恢复。重做日志使用WAL(log-ahead,write-to-log)。所有更改都会先写入日志,然后更新到缓冲池,确保不会因MySQL中断而丢失数据,从而满足持久性要求。
既然事务提交时重做日志也必须将日志写入磁盘,为什么它比直接将变化的数据写入磁盘上的缓冲池(即清理污垢)要快呢?主要有两个原因:
1。清除是随机IO,因为每次改变的数据位置是随机的,而写入重做日志是追加操作,属于顺序IO。
2。污垢的单位是数据页(Page)。默认 MySQL 页大小为 16 KB。页面上的一个小改动就需要写入整个页面;而重做日志只包含真正需要写入的部分。 ,无效IO大大减少。
3。 redo log和binlog
我们知道MySQL中还有binlog(二进制日志),同样可以记录写操作,用于数据恢复,但它们有本质的区别:
1、功能不同:使用日志重做进行崩溃恢复,保证MySQL宕机不影响持久性; binlog用于时间点恢复,保证服务器能够基于时间点恢复数据。另外,binlog还用于主从复制。
2。不同级别:redo log 实现的是 InnoDB 存储引擎,而 binlog 是用 MySQL 服务器层实现的(见文章前面 MySQL 逻辑架构的介绍),支持 InnoDB 等存储引擎。 。
3。内容不同:重做日志是物理日志,内容是基于磁盘页的; binlog是逻辑日志,内容是sql的一部分。
4。写入时间不同:事务批准时写入binlog;重做日志写入时间相对不同:
已经提到了:事务提交时会调用fsync来进行redo日志清空;这是默认策略。更改innodb_flush_log_at_trx_commit参数可以改变策略,但不能保证事务持久性。
除了提交事务之外,还有其他磁盘刷新时间表:例如主线程每秒刷新一次重做日志等。这样做的好处是不必等待提交刷新磁盘,提交速度大大加快。
4。绝缘
1。定义
与原子性和持久性重点研究事务本身不同,隔离性研究不同事务之间的交互。隔离性是指一个事务内的操作与其他事务是隔离的,并发的事务之间不能相互干扰。严格隔离对应于事务隔离级别的Serialized(可串行化),但由于性能方面的考虑,在实际应用中很少使用Serialized。
隔离力求并发情况下事务之间互不干扰。为了简单起见,我们只考虑最简单的读写操作(暂时忽略锁定读等特殊操作)。那么隔离性讨论主要可以分为两个方面:
- (事务)写操作对(其他事务)写操作的影响:锁定机制提供隔离性
- (事务)写操作对(其他事务)的影响读操作:MVCC 提供隔离
2。锁机制
首先我们看一下两个事务的写操作之间的交互。隔离性要求一次只有一个事务可以写入数据。 InnoDB 通过锁定机制提供此功能。
锁机制的基本原理可以概括为:一个事务在修改数据之前,必须获得一个合适的锁;获取锁后,事务可能会改变数据;在事务操作期间,这部分数据被锁定,如果其他事务需要更改数据,则应在当前事务提交或回滚时释放锁定。
行锁和表锁
根据碎片,锁可以分为表锁、行锁以及介于两者之间的其他锁。表锁在操作数据时锁定整个表,并发性较差;行锁只锁定需要管理的数据,并发性能好。但由于加锁本身会消耗资源(获取锁、检查锁、释放锁等都消耗资源),所以当锁定数据较多时,使用表锁可以节省大量资源。MySQL中不同的存储机制支持不同的锁。例如MyIsam只支持表锁,而InnoDB则同时支持表锁和行锁。出于性能原因,大多数情况下都会使用行锁。
如何查看锁信息
在InnoDB中查看锁状态的方法有很多种,例如:
select * from information_schema.innodb_locks; #锁的概况
show engine innodb status; #InnoDB整体状态,其中包括锁的情况复制代码
我们看一个例子:
#在事务A中执行:
start transaction;
update account SET balance = 1000 where id = 1;
#在事务B中执行:
start transaction;
update account SET balance = 2000 where id = 1;复制代码
查看当前锁状态:
show engine innodb status 检查与锁相关的部分:
可以通过上面的命令查看事务24052和24053的锁占用情况;其中lock_type为RECORD,表示该锁为行锁(记录锁); lock_mode 为 X,表示仅锁定(写锁定)。
除了排它锁(写锁)之外,MySQL还有共享锁(读锁)的概念。由于本文重点介绍MySQL事务的实现原理,所以关于锁的介绍到此结束。下面会专门写文章来分析MySQL中各种锁的区别和使用场景。我们邀请您关注。
介绍完写操作之间的交互,我们来讨论一下写操作对读操作的影响。
3。脏读、不可重复读和幻读
首先我们看一下并发情况下读操作可能存在的三类问题:
脏读: 可以在当前事务中读取(A)对于来自其他事务(B)的不相关数据(脏数据),这种现象就是脏读。举例如下(以账户余额表为例):
不重复的读取:相同的数据在事务A中读取两次,两次读取的结果不同。这种现象称为不可重复读取。脏读和不可重复读的区别在于,脏读读的是其他事务未提交的数据,而后者读的是其他事务已发送的数据。示例如下:
幻读: 在事务A中,根据一定条件查询数据库两次。两次查询的结果数量不同。这种现象称为幻读。不可重复读和幻读的区别可以简单理解为:前者表示数据发生了变化,后者表示数据的行数发生了变化。例如:
4。事务隔离级别
SQL标准定义了四种隔离级别,并指定了每个隔离级别以下是否存在上述问题。一般来说,隔离级别越低,系统的负载越低,可以支持的并发量就越多,但隔离性也就越差。隔离级别与读问题的关系如下:
在实际应用中,未提交读会导致很多并发问题,但相对于其他隔离级别,性能提升非常有限,所以很少使用。 Serialized 强制事务序列化,并发效率很低。只能在数据一致性要求极高且无法接受并发的情况下使用,所以很少使用。因此,在大多数数据库系统中,默认的隔离级别是提交读(如Oracle)或可重复读(以下简称RR)。
全局隔离级别和本次会话的隔离级别可以通过以下两个命令查看:
InnoDB默认的隔离级别是RR,后面会介绍RR。需要注意的是,SQL RR标准无法避免幻读问题,但是InnoDB实现的RR却避免了幻读问题。
5。 MVCC
RR解决了脏读、不可重复读、幻读等问题。使用MVCC:MVCC的全称是Multi-Version Concurrency Control,是一种多版本并发控制协议。下面的例子很好地说明了MVCC的特点:同时,不同事务读取的数据可以不同(即多个版本)——在T5时刻,事务A和事务C可以读取不同的版本。数据。
MVCC最大的优点就是读不加锁,所以读和写不会冲突,并发性好。 InnoDB实现了MVCC,数据可以有多个版本,主要依靠隐藏数据列(也称为标签位)和undo log。隐藏数据列包括数据行版本号、删除时间、undo log指针等; MySQL在读取数据时,可以通过隐藏列来判断是否需要回滚,并找到回滚所需的undo log。这就是MVCC的实现方式;隐藏列的冗长格式不再普遍存在。
下面根据上述几个问题分别进行说明。
脏读
当事务A在时间节点T3读取zhangsan的状态时,会发现数据已被其他事务修改,状态未赋值。此时,事务A读取到最新的数据后,会根据数据undo log进行回滚操作,取回事务B发生变化之前的数据,从而避免了脏读。
不可重复读
当事务A第一次在节点T2读取数据时,会记录数据版本号(数据版本号以行为单位写入),假设版本号为1;当事务B提交时,该行记录的版本号递增,假设版本号为2;当事务A在T5再次读取数据时,发现数据(2)的版本号大于第一次读取时记录的版本号。不。 (1),所以当版本号为1时,会根据undo log进行回滚操作来检索数据,从而实现可重复读取。 InnoDB实现的
幻读
RR通过下一键锁定机制避免了幻读的发生。
下一个键锁是行锁的一种,相当于记录锁+空间锁;它的特点是不仅锁定记录本身(记录锁定功能),还锁定范围A(间隙锁定功能)。当然,这里我们说的是解锁读:此时下一个key的锁并没有真正锁定,它只是在读取的数据上添加了一个标签(标签的内容包括数据的版本号, ETC。) ;为了准确起见,我们称其为带有以下钥匙的锁机构。我们用前面的例子来说明:
当事务 A 首先在节点 T2 上读取 0
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。