MySQL系列(五)— MySQL事务及原理精讲

前言

前面我们花了大量篇幅详细精讲了一下MySQL索引及SQL调优的内容,今天我们再来学习一下MySQL另一个很重要的技术——事务。虽然平时工作中,MySQL事务没有索引那么频繁直接出现在我们面前,但是有个一两年工作经验的人都接触过Spring事务,Spring事务所采用的技术我们以后另找时间详细介绍一下,你知道么,其实Spring事务底层最终也是借用MySQL事务的。下面我们开启我们的MySQL事务之旅吧!

一、什么是事务?

1、事务的含义

俗话说“知己知彼,方能百战百胜”。我们先来看一下什么是事务?所谓事务,大白话讲就是:一组操作要么全部成功,要么全部失败。对MySQL来说就是:一组SQL要么全部执行成功,要么全部不执行(或失败回滚)。

2、事务的四大特性

事务的四大特性也叫事务的四大属性,简称ACID,如下:
原子性(Atomicity):一个事务内的所有操作的执行要么同时成功,要么同时失败。举个例子:转账功能中,转入账户收到一百万人民币,转出账户就必须减少一百万人民币。MySQL的原子性借助undo log日志来实现。
一致性(Consistent):事务前后数据的完整性必须保持一致。
隔离性(Isolation):多个事务并发执行时,各个事务内部的操作相互独立不能互相干扰。MySQL的隔离性由MySQL的各种锁以及MVCC机制来实现。
持久性(Durable):事务一旦提交,它对数据库中数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。MySQL的持久性由redo log日志来实现。

二、事务并发带来的问题

由于在MySQL中的事务是由存储引擎实现,而且MySQL只有InnoDB存储引擎支持事务。因此我们讲解的事务都是基于InnoDB存储引擎的。
数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,如果不加限制,就会导致一系列问题:

1、更新丢失(Lost Update)或脏写

当两个或多个事务对同数据的同一个值进行更新该行时,由于每个事务都不知道其他事务的存在,就会发生最后一个事务的更新覆盖了由其他事务所做的更新。
概括为一句话:对同一个数据的同一个值操作时,事务B的操作覆盖了事务A的操作。

2、脏读(Dirty Reads)

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。
概括为一句话:事务B读取到了事务A已经修改但尚未提交的数据。不符合一致性要求。

3、不可重读(Non-Repeatable Reads)

同一个事务内在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了,这种现象就叫做“不可重复读”。
概括为一句话:同一个事务内,相同的查询语句在不同时刻查询的结果不一致。不符合隔离性

4、幻读(Phantom Reads)

同一个事务内按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
概括为一句话:事务B读取到了事务A已经提交的新增数据。不符合隔离性。

三、事务隔离级别

为了解决多事务并发带来的问题,MySQL提出了事务隔离级别,每个隔离级别都对应的解决了一些问题,看下表:

隔离级别脏读(Dirty Read)不可重复度幻读(Phantom Read)
读未提交(Read uncommitted)可能可能可能
读已提交(Read committed)不可能可能可能
可重复读(Repeatable read)不可能不可能可能
可串行化(Serializable)不可能不可能不可能

安全和性能对比:
安全性: 可串行化 > 可重复度 > 读已提交 > 读未提交;
性 能: 可串行化 < 可重复读 < 读已提交 < 读未提交;
常见数据库的默认隔离级别:
MySql:可重复度;
Oracle:读已提交;
数据库的事务隔离越严格,并发副作用就越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复
读"和“幻读”并不敏感,可能更关心数据并发访问的能力。
各位可以采用以下命令查看和设置自己的数据库的事务隔离级别;
查看当前数据库的事务隔离级别: show variables like ‘tx_isolation’;
设置事务隔离级别为可重复读:set tx_isolation=‘REPEATABLE-READ’;
Mysql默认的事务隔离级别是可重复读,用Spring框架开发程序时,如果不设置隔离级别,则会默认用Mysql设置的隔离级别,如果Spring设置了就用已经设置好的隔离级别。

四、MySQL事务底层原理详解

针对事务并发带来的问题,MySQL提出了事务隔离级别,在三中我们也介绍了各种隔离级别及其解决了哪些问题。相信你此时会有一个问题浮现在脑海里,事务隔离级别是如何实现的呢?带着这个问题,我们以MySQL默认隔离级别为例来一探究竟!

1、 提出问题

假如现在数据库中有三个账户:A、B、C;账户A向账户B转10000万元,同时账户B向账户C转5000元,如果不加控制或没有事务的话,这个看似简单的转账功能很可能会出各种问题,比如:转账前后三个账户的总金额不一致;账户金额增加或减少的金额不对等。
带着这个转账问题,我们来看一下MySQL是如何解决的。

2、 解决方案

方案一、锁机制,这个是我们最容易想到的;
方案二、MVCC多版本并发控制机制;
关于锁机制,我们下一篇文章会做详细介绍,今天我们先来介绍一下MVCC多版本并发控制机制;

3、 MVCC多版本并发控制机制原理

MySQL在可重复读事务隔离级别下为了保证事务较高的隔离性,采用了MVCC (Multi-Version Concurrency Control) 机制来保证的。MVCC全称叫做多版本并发控制,是RDBMS常用的一种并发控制方法,用来对数据库数据进行并发访问,实现事务。其核心思想是读不加锁,读写不冲突。对同一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥造成并发性能下降(而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的)。
MySQL在读已提交和可重复读隔离级别下都实现了MVCC机制。
MVCC实现原理是数据快照,不同的事务访问不同版本的数据快照,从而实现事务下对数据的隔离级别。虽然说具有多个版本的数据快照,但这并不意味着必须拷贝数据,保存多份数据文件(这样会浪费存储空间),InnoDB存储引擎通过事务的Undo日志巧妙地实现了多版本的数据快照。
MVCC的实现依赖于Undo日志和Read View。下面我们先来详细介绍一下这两个机制。
1)、Undo日志
Undo存放在数据库内部的一个特殊段(segment)中,这个段称为Undo段(undo segment)。Undo段位于系统表空间内,也可以设置为Undo表空间。
Undo日志保存了记录修改前的数据,并且用两个隐藏字段trx_id和roll_pointer把这些Undo日志串联起来形成一个历史记录版本链(参考图1)。所以,对于更新和删除操作,InnoDB存储引擎并不是真正的删除原来的记录,而是设置记录的delete mark为1。
在这里插入图片描述

2)Read View
ReadView其实就是一张存储事务id的表。在可重复读隔离级别,当事务开启后,执行任何查询SQL时都会生成当前事务的一致性视图Read View,该视图在事务结束之前永远都不会变化(如果是读已提交隔离级别则在每次执行查询SQL时都会重新生成一致性视图Read View)。这个视图包含的内容有:执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)如图2。事务里的任何SQL查询结果需要从对应的Undo日志版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。
在这里插入图片描述

3)版本链比对规则:
要结合图1和图2一起来看,那图1的每一行数据row跟图2比较:
a、如果row的trx_id落在最左边部分,即trx_id<min_id,表示这个版本是已提交的事务生成的,该数据是可见的;
b、如果row的trx_id落在最右边部分,即trx_id>max_id,表示这个版本是由将来启动的事务生成的,是不可见的(若row的trx_id就是当前自己的事务则是可见的);
c、如果row的trx_id落在中间部分,即min_id<=trx_id<=max_id,分为两种情况:
一是:若row的trx_id在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若row的trx_id就是当前自己的事务是可见的);
二是:若row的trx_id不在视图数组中,表示这个版本是已经提交了的事务生成的,该数据是可见的。
举例说明: 假如此时有一张表account表,其属性有:id、name、balance;并且也有一条id为1余额为0的基础数据。如下图:
在这里插入图片描述

横轴为不同客户端开启的事务,纵轴为时间(从上到下按照从早到晚的顺序)。
当#select 1在执行select balance from account where id = 1时,会生成一致性视图Read View,因为此时事务100和事务200还未提交,事务300已经提交,且已生成的最大事务为300,所以生成的一致性视图Read View为:[100,200] 300;Undo日志版本链为:
在这里插入图片描述

将Undo日志版本连的每一条数跟生成的Read View[100,200] 300做对比,此时最小事务id为100,最大事务id为300,未提交的事务id数组为[100,200];根据上面的比对规则就可得出:先拿第5条数据做对比,第5条数据事务id为300,满足min_id < 300 <= max_id且300不在[100,200]数组内,因此此条数据是可见的;紧接着第4、3、2条数据,由于Undo版本连上的事务200和100都在[100,200]因此是不可见的,而事务99满足99 < min_id,是已经提交了的数据,因此也是可见的,所以最终查询的结果为500。由于在可重复读隔离级别下,同一个事务内生成的Read View是不变动的,所以selec 1中再次查询相同的语句,得出的结果还是一样的。
以上便是根据select 1分析的过程,各位可以自己根据select 2分析以加强自己的理解。
所有由此我们可以看到:Read View和可见性算法其实就是记录了SQL查询那个时刻数据库里提交和未提交所有事务的状态。要实现RR隔离级别,事务里每次执行查询操作Read View都是使用第一次查询时生成的Read View,也就是都是以第一次查询时当时数据库里所有事务提交状态来比对数据是否可见,当然可以实现每次查询的可重复读的效果了。要实现RC隔离级别,事务里每次执行查询操作Read View都会按照数据库当前状态重新生成Read View,也就是每次查询都是跟数据库里当前所有事务提交状态来比对数据是否可见,当然实现的就是每次都能查到已提交的最新数据效果了。
注意: begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作或加排它锁操作(比如select…for update)的语句时,事务才真正启动,才会向MySQL申请真正的事务id,MySQL内部是严格按照事务的启动顺序来分配事务id的。

以上内容便是MySQL事务及事务底层原理MVCC机制的详细介绍,下一节我们来详细介绍一下MySQL事务的另一个实现方案——锁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值