MySQL深度解析--事务隔离级别

MySQL事务隔离

Ⅰ.四种隔离级别

1.读未提交

一个事务还没提交时,它做的变更就能被别的事务看到。

2.读已提交

一个事务提交之后,它做的变更才会被其他事务看到。

3.可重复读

一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

4.串行化

顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

Ⅱ.隔离机制分析

读未提交

直接返回记录上的最新值

读已提交

每个SQL语句执行时会创建一个视图,访问时以视图的逻辑为准

可重复读

事务启动时创建一个视图,整个事务存在期间都使用这个视图中的数据
PS:可重复读的状态下,数据库中同一行势必会存在不同的版本,也被称作数据库多版本并发控制(MVCC),视图通过回滚日志和当前值来得到指定版本的数据。

串行化

直接用加锁的形式来避免并发访问

Ⅲ.一些问题以及解决方法

Oracle迁移到MySQL
Oracle数据库的默认隔离级别其实就是“读提交”,因此对于一些从Oracle迁移到MySQL的应用,为保证数据库隔离级别的一致,你一定要记得将MySQL的隔离级别设置为“读提交”。
配置的方式是,将启动参数transaction-isolation 的值设置成READ-COMMITTED。你可以用show variables 来查看当前的值。

mysql> show variables like 'transaction_isolation';

+-----------------------+----------------+

| Variable_name | Value |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

+-----------------------+----------------+

回滚日志体积过大
回滚日志删除的条件:
当没有事务需要用到这些回滚日志时(系统里没有比这个回滚日志更早的read-view视图),回滚日志将被删除。

基于上面的规则,建议尽量不要使用长事务。长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。同时,长事务还占用锁资源,也可能拖垮整个库。

在MySQL 5.5及以前的版本,回滚日志是跟数据字典一起放在ibdata文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。


事务的启动方式

事务启动方式有以下几种:

  • set autocommit=1 显式启动事务语句,begin 或 start transaction。配套的提交语句是commit,回滚语句是rollback。

  • set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个select语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行commit 或 rollback 语句,或者断开连接。

PS:为了避免长连接,建议总是使用set autocommit=1, 通过显式语句的方式来启动事务。

对于频繁使用事务的业务,可以使用 commit work and chain 提交事务并自动开启下一个事务。
这样会省去后面事务执行begin语句的开销。


查找长事务
你可以在information_schema库的innodb_trx这个表中查询长事务。

#用于查找持续时间超过60s的事务
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60 

Ⅳ.事务隔离的底层实现

一些概念说明

事务开始begin/start transaction 只是表示事务开始,并不代表事务启动

事务启动 :执行事务中第一个读/写语句,或者使用 start transaction with consistent snapshot 命令。

transaction_id :事务ID。每个事务都有一个唯一的事务ID,它是事务开始时向InnoDB的事务系统申请的,是严格按照申请顺序递增的。

row trx_id :数据行的版本号,一行有多个row trx_id 分别对应多个版本。事务在更新数据时,会生成一个新的数据版本,并把自己的transaction_id赋值给新数据版本的row trx_id

更新数据示意图:
PS :U1、U2、U3是 undo log 回滚日志。
V1、V2、V3并不是真实存在的,是通过最新版本和undo log 日志恢复而来的版本。
行状态变更图


可重复读快照的底层实现

InnoDB会在每个事务启动的时候,为该事务构造一个数组,用来保存这个事务启动瞬间,当前启动了但还没提交的事务ID

注意这里数组保存的是启动事务的ID,不是开始事务的ID

数组里面 事务ID的最小值 记为低水位,当前系统里面已经创建过的 事务ID的最大值 加1记为高水位

由于事务开始时申请事务ID,事务启动时构造数组,中间有一定的时间差,所以当前事务的ID只能在低水位和高水位之间

这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。
在这里插入图片描述

而数据版本的可见性规则,就是基于数据的row trx_id和这个一致性视图的对比结果得到的。

这个视图数组把所有的row trx_id 分成了几种不同的情况:

  1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
  2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
  3. 如果落在黄色部分,那就包括两种情况
    a. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见
    b. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见

读已提交快照的底层实现

读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:
在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。


事务的更新逻辑

上面说了可重复读级别下,事务的数据可以互不影响。但是当事务中有更新(update)操作时,规则会有一定变化。

事务更新必须在最新的版本更新,更新需要先读到最新的数据,然后写数据。

更新前的读数据叫做“当前读” ,当前读要获取的是写锁(行锁),如果要读取的行上已经有写锁,那么当前读这个操作会进入锁等待

#除了事务的更新会当前读以外,也可以手动 "当前读"
select k from t where id=1 lock in share mode;# 加了读锁(S锁,共享锁)
select k from t where id=1 for update; # 加写锁(X锁,排他锁)

事务更新实例
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值