文章目录
前言
首先我们知道MySql是C/S架构的;多个客户端可以连接到同一个mySql服务,客户端包括不限于图形化管理界面(navicat、
Dbeaver...)、命令行客户端(mysql -uroot -p)、java代码里的JDBC......;其中一次连接就是一次session会话,
而事务(注意innodb引擎才支持事务,默认开启事务且是自动提交)的作用范围就是在一个session中。
多个事务会引发的问题
也就是对于服务器来说可能同时处理多个事务,类似于Java中的多线程,这样就会引发出一些问题
脏读
当一个事务读取到了另外一个事务修改但未提交的数据,被称为脏读
在事务A执⾏过程中,事务A对数据资源进⾏了修改,事务B读取了事务A修改后的数据。 2、由于某些原因,事务A并没有完成提交,发⽣了RollBack操作,则事务B读取的数据就是脏数据。 这种读取到另⼀个事务未提交的数据的现象就是脏读(Dirty Read)。
不可重复读
当事务内相同的记录被检索两次,且两次得到的结果不同时,此现象称为不可重复读
事务B读取了两次数据资源,在这两次读取的过程中事务A修改了数据,导致事务B在这两次读取出来的 数据不⼀致
幻读
在事务执行过程中,另一个事务将新记录添加到正在读取的事务中时,会发生幻读。
事务B前后两次读取同⼀个范围的数据,在事务B两次读取的过程中事务A新增了数据,导致事务B后⼀ 次读取到前⼀次查询没有看到的⾏。 幻读和不可重复读有些类似,但是幻读重点强调了读取到了之前读取没有获取到的记录。
事务隔离级别
为了解决多事务引发的问题,mysql提供了几种事务隔离级别
mySql默认隔离级别:REPEATABLE READ
READ UNCOMMITTED隔离级别下,可能发生脏读、不可重复读和幻读问题。
READ COMMITTED隔离级别下,可能发生不可重复读和幻读问题,但是不可以发生脏读问题。
REPEATABLE READ隔离级别下,可能发生幻读问题,但是不可以发生脏读和不可重复读的问题。能解决部分幻读问题(间隙锁)
SERIALIZABLE隔离级别下,各种问题都不可以发生。
查看当前隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
这些隔离级别就是通过MVCC来实现的
MVCC
MVCC(Multi-Version Concurrency Control) ,叫做基于多版本的并发控制协议。他是和LBCC(Lock-Based ConcurrencyControl)基于锁的并发控制概念是相对的。
MVCC是乐观锁的一种实现方式,它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
MVCC最大的好处:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能,现阶段几乎所有的RDBMS包括MySQL,都支持了MVCC。
MVCC实现原理主要是依赖记录中的隐式字段,undo日志,Read View 来实现的。
开始之前需要先知道的几个概念
版本链
我们知道,对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列(row_id并不是必要的,
我们创建的表中有主键或者非NULL的UNIQUE键时都不会包含row_id列):
trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。
roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
undo日志:
为了实现事务的原子性,InnoDB存储引擎在实际进行增、删、改一条记录时,都需要先把对应的undo日志记下来。
一般每对一条记录做一次改动,就对应着一条undo日志,但在某些更新记录的操作中,也可能会对应着2条undo日志。
一个事务在执行过程中可能新增、删除、更新若干条记录,也就是说需要记录很多条对应的undo日志,这些undo日志会被 从
0开始编号,也就是说根据生成的顺序分别被称为第0号undo日志、第1号undo日志、...、第n号undo日志等,这个编号也被称之为undo no
一条数据的实际存储,还有其他字段(rowId)…
版本链:由以下伪代码的结果
begin transaction 60
insert into user (1,张三,四川省)
commit
begin transaction 80
update user set (1,张三,陕西省)
update user set (1,张三,湖南省)
commit
begin transaction 120
update user set (1,张三,贵州省)
update user set (1,张三,山东省)
commit
会生成如下版本链
READ VIEW
开启事务的操作后会生成,ReadView中主要包含4个比较重要的内容
m_ids:
表示在生成ReadView时当前系统中活跃的读写事务的事务id列表
活跃也就是还没提交的事务
min_trx_id:
表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值
max_trx_id:
表示生成ReadView时系统中应该分配给下一个事务的id值。注意max_trx_id并不是m_ids中的最大值,事务id是递增分配 的。
比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1 和
2,min_trx_id的值就是1,max_trx_id的值就是4
creator_trx_id:
表示生成该ReadView的事务的事务id
如何使用
通过当前事务的id和readView中的几个值比较判断能否读取其他事务的数据
当前事务去查询数据:
如果被访问记录版本的trx_id小于readView中的min_trx_id,表明生成该版本的事务在当前事务生
成ReadView前已经提交,所以该版本可以被当前事务访问
如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,
所以该版本可以被当前事务访问。
未提交读(READ UNCOMMITTED)
每次读取最新版本的记录
在时刻1查询数据:版本连中只有trx_id为60的记录,此时读取这条数据
在时刻2查询数据:版本连中存在两条记录,读取最新的trx_id是80的数据
…
已提交读(READ COMMITTED)
事务中的每次读取操作都会新生成readView
trx_id是60的事务在时刻1提交
事务X在时刻1读取数据,会生成一个readView:
m_ids集合包括:[],因为trx_id是60的事务已经提交, 直接读取这条数据
trx_id是60的事务在时刻1提交
trx_id是80的事务在时刻3提交
事务X在时刻2再次查询数据:这个时刻又会新生成一个readView
m_ids集合包括:[80],因为trx_id为80的事务此时刻还未提交
此时版本连中最新记录的trx_id是80,不小于m_ids中的最小id,还是只能读取值是"四川省"的记录
trx_id是60的事务在时刻1提交
trx_id是80的事务在时刻3之后才提交
事务X在时刻3读取数据:此时又会新生成一个readView
m_ids集合包括:[80],因为trx_id是80的事务这个时刻还未提交
此时版本连中最新记录的trx_id是80,不小于m_ids中的最小id,还是只能读取值是"四川省"的记录
trx_id是60的事务在时刻1提交
trx_id是80的事务在时刻4之后提交
trx_id是80的事务在时刻5之后提交
事务X在时刻4读取数据:会在此时刻重新生成一个readView
m_ids集合包括:[80,120],因为这两个事务在时刻4都未提交
此时版本连中最新记录的trx_id是80,不小于m_ids中的最小id,还是只能读取值是"四川省"的记录
trx_id是60的事务在时刻1提交
trx_id是80的事务在时刻4提交
trx_id是80的事务在时刻5之后提交
事务X在时刻5读取数据:会在此时刻重新生成一个readView
m_ids集合包括:[120],因为在时刻4只有trx_id为120的事务未提交
此时版本连中最新记录的trx_id是120,不小于m_ids中的最小id,trx_id是80的事务已经提交,可以读取值是"湖南省"的记录
可重复读(REPEATABLE READ)
只会在事务开启的第一次读取操作生成readView
trx_id是60的事务在时刻1提交
trx_id是80的事务在时刻4提交
trx_id是80的事务在时刻5提交
事务X在时刻2开启并开始第一次读取数据操作,此时会生成一个readView,并且在事务结束前都不会重新生成readView
从时刻1到时刻5的四次查询都会使用第一次生成的readView
m_ids集合:[80,120],这两个事务在时刻2都还未提交
只有trx_id为60的记录才小于集合中的最小值,这四次查询都只能查询到值为"四川省"的记录
解决换段(SERIALIZABLE)
就是悲观锁的实现:直接加锁
总结
通过readView和版本链比较:
1、如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
2、如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
不同隔离级别生成readView和比较方式稍有不同
读未提交:每次直接读取版本连中的最新记录
读已提交:每次读取操作都会新生成一个readView,再拿要访问事务的id和readView做比较来判断能否访问到
可重复读:只在事务的第一次读取操作生成readView(事务结束前的读取操作共用一个readView),再拿要访问事务的id和
readView做比较来判断能否访问到
串行化:直接加锁让事务一个一个顺序执行