mysql事务的一个简单介绍

Mysql事务

  • 基础概念

    事务(transaction)是 访问和更新数据库的程序执行单元,事务中可能包含 一个或多个sql语句,这些语句要么都执行,要么都不执行。作为一个关系行数据库,Mysql支持事务

    1. 逻辑架构和存储引擎

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z2vakpFm-1589247064544)(/blog/img/sw1.png)]

    图片来源:https://blog.csdn.net/fuzhongmin05/article/details/70904190

    如上图所示,MySQL服务器逻辑架构从上往下可以分为三层:

    (1)第一层:处理客户端连接、授权认证等。

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

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

    1. ACID特性

    ACID是衡量事务的四个特性:

    • 原子性(Atomicity,或称不可分割性)
    • 一致性(Consistency)
    • 隔离性(Isolation)
    • 持久性(Durability)

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


  • 原子性

    1. 定义

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

    1. 实现原理:undo log

    在说明原子性原理之前,首先介绍一下MySQL的事务日志。MySQL的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等,此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。

    下面说回undo log。实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback**,导致事务需要回滚,便可以利用undo log****中的信息将数据回滚到修改之前的样子。**

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

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


  • 持久性

    1. 定义

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

    1. 实现原理:redo log

    redo log和undo log都属于InnoDB的事务日志。下面先聊一下redo log存在的背景。

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

    Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。

    于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。

    既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:

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

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

    1. redo log与binlog

    我们知道,在MySQL中还存在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的写入时机相对多元:

    • 前面曾提到:当事务提交时会调用fsync对redo log进行刷盘;这是默认情况下的策略,修改innodb_flush_log_at_trx_commit参数可以改变该策略,但事务的持久性将无法保证。
    • 除了事务提交时,还有其他刷盘时机:如master thread每秒刷盘一次redo log等,这样的好处是不一定要等到commit时刷盘,commit速度大大加快。

  • 隔离性

    1. 定义

    **与原子性、持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。**隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。严格的隔离性,对应了事务隔离级别中的Serializable (可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。

    隔离性追求的是并发情形下事务之间互不干扰。简单起见,我们仅考虑最简单的读操作和写操作(暂时不考虑带锁读等特殊操作),那么隔离性的探讨,主要可以分为两个方面:

    • (一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
    • (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性
    1. 锁机制

    首先来看两个事务的写操作之间的相互影响。隔离性要求同一时刻只能有一个事务对数据进行写操作,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;
    

    此时查看锁的情况:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eqD5FRIB-1589247064547)(/blog/img/sw2.png)]

    show engine innodb status查看锁相关的部分:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SQwxXH81-1589247064548)(/blog/img/sw3.png)]

    通过上述命令可以查看事务24052和24053占用锁的情况;其中lock_type为RECORD,代表锁为行锁(记录锁);lock_mode为X,代表排它锁(写锁)。

    除了排它锁(写锁)之外,MySQL中还有共享锁(读锁)的概念。由于本文重点是MySQL事务的实现原理,因此对锁的介绍到此为止,后续会专门写文章分析MySQL中不同锁的区别、使用场景等,欢迎关注。

    介绍完写操作之间的相互影响,下面讨论写操作对读操作的影响。


    3. 事物隔离级别

    3.1 未提交读: 脏读(READ UNCOMMITTED)

    1)事务2查询到的数据是事务1中修改但未提交的数据,但因为事务1回滚了数据

    2)所以事务2查询的数据是不正确的,因此出现了脏读的问题。

    3.2 提交读: 不可重复读(READ COMMITTED)

    1)事务2执行update语句但未提交前,事务1的前两个select操作返回结果是相同的。

    2)但事务2执行commit操作后,事务1的第三个select操作就读取到事务2对数据的改变。

    3)导致与前两次select操作返回不同的数据,因此出现了不可重复读的问题。

    4.3 可重复读: 幻读(REPEATABLE READ):这是MySQL的默认事务隔离级别

    1)事务每开启一个实例,都会分配一个版本号给它,如果读取的数据行正在被其它事务执行DELETE或UPDATE操作(即该行上有排他锁)

    2)这时该事物的读取操作不会等待行上的锁释放,而是根据版本号去读取行的快照数据(记录在undo log中)

    3)这样,事务中的查询操作返回的都是同一版本下的数据,解决了不可重复读问题。

    4)虽然该隔离级别下解决了不可重复读问题,但理论上会导致另一个问题:幻读(Phantom Read)。

    5)一个事务在执行过程中,另一个事物对已有数据行的更改,MVCC机制可保障该事物读取到的原有数据行的内容相同

    6)但并不能阻止另一个事务插入新的数据行,这就会导致该事物中凭空多出数据行,像出现了幻读一样,这便是幻读问题。

    3.4 可串行读(SERIALIZABLE)

    1)这是事务的最高隔离级别,通过强制事务排序,使之不可能相互冲突,就是在每个读的数据行加上共享锁来实现

    2)在该隔离级别下,可以解决前面出现的脏读、不可重复读和幻读问题,但也会导致大量的超时和锁竞争现象,一般不推荐使用


  • 一致性

    1. 基本概念

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

    1. 实现

    可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。

    实现一致性的措施包括:

    • 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证
    • 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等
    • 应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致

    • 总结

      下面总结一下ACID特性及其实现原理:

      • 原子性:语句要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的;实现主要基于undo log
      • 持久性:保证事务提交后不会因为宕机等原因导致数据丢失;实现主要基于redo log
      • 隔离性:保证事务执行尽可能不受其他事务影响;InnoDB默认的隔离级别是RR,RR的实现主要基于锁机制、数据的隐藏列、undo log和类next-key lock机制
      • 一致性:事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值