在MySQL中有四个标准隔离级别:
隔离级别越高,隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。
当数据库中一个事务A正在修改一个数据但是还未提交或者回滚时,另一个事务B 来读取了修改后的内容并且使用了,然后事务A进行了提交,此时就引起了脏读。
此情况仅会发生在: 读未提交的的隔离级别。
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻读。详细的讲:事务A第一次读取了一个区间数据,一共有100条。这是事务B又写入了一条,并且提交了。事务A再次同一个区间时,此时一共有101行数据。就发生了幻读。
此情况会发生在:读未提交、读提交、可重复读的隔离级别。(有办法解决)
特别说明:
在一个事务A中读取一行数据,在事务操作过程中(未提交),事务B对该行数据处理更新,该行数据发生了改变,然后事务A再读取这行数据时,发现数据与第一次不一样了。
此情况仅会发生在:读未提交、读提交的隔离级别。
我们首先来了解下‘当前读’和‘快照读’,这两个概念我们后续要用到很多次。
一般情况下,不加锁的select操作,就是快照读。快照的前提是隔离级别不是串行化,在串行化下快照读会退化为当前读。快照读是一种不加锁的非阻塞读, 用以提高并发。它的实现是基于MVCC(多版本并发控制),他的读取到的数据不一定是最新的数据,很有可能是之前的旧数据。
快照读可以做到读取数据时不加锁,这样读写并发就不会有并发安全问题,也不会因为加锁存在性能问题。代价就是增加逻辑上的复杂度。
这几类操作都是当前读,即读取到当前最新的有效的数据版本,读取时加锁,保证其他事务不会操作当前的数据。
这个字段会在事务开始的时候向 ”InnoDB 的事务系统“申请的,是*按申请顺序严格递增*的。而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。
引申 trx_id 自增满了,怎么办?
三个虚线箭头,就是 undo log;而 V1、V2、V3 并不是物理上真实存在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。比如,需要 V2 的时候,就是通过 V4 依次执行 U3、U2 算出来。具体的这里不展开讲。
事务进行快照读操作的时候生成一个的读视图(Read View)。在该事务开始执行的时候,记录并维护系统当前的trx_id。这个视图记录着对于当前事务,哪些数据版本是可见的,哪些是不可见的。具体而言:
在实现上,InnoDB为当前启动的事务维护着一个数组,这个数组里放的是当前已经启动的,但是没有提交的事务ID。这里面最大的ID+1记做高水位,这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。
MVCC的视图主要是在‘可重复读’和‘读提交’这两种隔离级别下工作的。详细的讲,<u>当我们开启一个事务时,数据库里面会创建一个一致性视图(consistent read view)访问的时候以视图的逻辑结果为准。</u>在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
特别说明:
我们需要注意的是事务的启动时机。通常情况下,begin/start transaction 命令并不是一个事务的起点,在执行到之后的第一个操作 InnoDB 表的SQL语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。
第一种启动方式,一致性视图是在执行第一个快照读语句时创建的;
第二种启动方式,一致性视图是在执行 start transaction with consistent snapshot 时创建的。
更新一行数据时,会给该行数据加上行锁,但是新插入数据的时候,是在多行数据之间,此时就需要用到间隙锁(Gap Lock)。
引入间隙锁可以有效解决幻读的问题,但是会影响到数据库的效率
MySQL 的事务启动方式有以下几种:
如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销。
#这个SQL 可以获取执行时间超过1分的长事务
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
理论上,只要mysql 跑的足够的久,这个trx_id 一定会出现满了的情况。只不过这个时间非常非常的久。
trx_id 的上限是2的48次方-1
另外,对于一个只读的事务,是不会分配trx_id 的,这样可以降低增长。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。