赞
踩
读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。
读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。
可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。
串行:我的事务尚未提交,别人就别想改数据。
这4种隔离级别,并行性能依次降低,安全性依次提高。
1 脏读
脏读发生在一个事务A读取了被另一个事务B修改,但是还未提交的数据。假如B回退,则事务A读取的是无效的数据。这跟不可重复读类似,但是第二个事务不需要执行提交。
2 不可重复读:
是指事务中,对于同一条数据,两次查询的结果不一致,原因,在查询的过程中,其他事务做了更新的操作。多指更新操作
- 将当前事务隔离级别设置为读已提交
-
- 1 现有一个学生表,有俩个字段(name,age)
- 2 插入数据insert into student(name,age) values('1',1)
- 3 新建俩个事务 tx1 tx2
- 4 tx1 进行查询 select age from student where name = '1' ## age 为 1
- 5 tx2 执行更新语句 update student set age = 2 where name= '1'
- 6 tx2 提交
- 7 tx1在进行查询 ## age 为2
-
-
- 结论:在同一个事务中,俩次查询的结果不一致,导致不可重复读。
-
-
3 幻读:
幻读是指在俩次快照读中间,有过一次或多次的修改,并且这次修改是其他事务已经提交并修改过程的数据或者是当前读,会导致俩次快照读不一致。
修改或者删除会导致新添一个undo log 版本链,所以当前事务在MVCC规则下,快照读可以看到修改后的数据。
对于约束性规则,如插入唯一索引的时候,也会进行当前读。
比如说我们有一个user 表,其中user_name 是唯一索引,进行如下操作。事务隔离级别为可重复读
例1
事务1 | 事务2 |
begin; insert into user(user_name) values (‘张三'); | |
begin; select * from user where user_name = '张三' 此时返回时空的 然后我们进行数据的插入; insert into user(user_name) values (‘张三'); // insert 需要进行当前读 这时会报错,唯一索引重复了 |
例2
事务1 | 事务2 |
begin; | begin |
select * from user where user_name = '张三'; // age = 10 ; | select * from user where user_name = '张三' // age 10 |
update user set age = age + 1 ; commit ; | |
update user set age = age + 1 ; select * from user where user_name = '张三' // age 12 |
解决:通过间隙锁来解决,比如说在例子二将查询语句改为这样,select * from user where user_name = '张三' for update ,那么在 事务1更新年龄的时候就会阻塞。
select * from information_schema.innodb_trx
4 mysql 事务的实现
事务实现是通过MVCC(多版本并发控制)来创建视图实现的。
这里的视图和表对应的视图不太一致
在 MySQL 里,有两个“视图”的概念:
它没有物理结构,作用是事务执行期间用来定义“我能看到什么数据”。
对于可重复读来说视图的启动时机不同:
- begin/start transaction
- start transaction with consistent snapshot
- 第一种启动方式,一致性视图是在第执行第一个快照读语句时创建的;
- 第二种启动方式,一致性视图是在执行 start transaction with consistent snapshot 时创建的。
对于读已提交的话,是在每次执行一个SQL的时候创建一个视图
视图的实现:
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。
DATA_TRX_ID:最近更新这条行记录的事务ID,6Byte
DATA_ROLL_PTR:表示指向该行回滚段(rollback segment)的指针,大小为 7 Byte
每次对数据进行更新操作时,都会 copy 当前数据,保存到 undo log 中。并修改当前行的回滚指针指向 undo log 中的旧数据行。
DB_ROW_ID:隐藏主键,6Byte
数据版本生成情况:
也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。 InnoDB的MVCC实现依赖 undo log 和 read view
update 或 delete 操作中产生的 undo log。因为会对已经存在的记录产生影响,为了提供 MVCC机制,因此update undo log 不能在事务提交时就进行删除,而是将事务提交时放到入 history list 上,等待 purge 线程进行最后的删除操作。
图中虚线框里是同一行数据的 4 个版本,当前最新版本是 V4,k 的值是 22,它是被 transaction id 为 25 的事务更新的,因此它的 row trx_id 也是 25。图中的三个虚线箭头,就是undo log ,可以利用它进行事务的回滚。
接下来具体看下视图的生成(一下为可重复读的分析,读已提交类似,只是视图生成的时机不同):
按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。
因此,一个事务只需要在启动的时候声明说,“以我启动的时刻为准,如果一个数据版本是在我启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版本”。
当然,如果“上一个版本”也不可见,那就得继续往前找。还有,如果是这个事务自己更新的数据,它自己还是要认的。
在实现上, InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务 ID。“活跃”指的就是,启动了但还没提交。
数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。
这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。
而数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到的。
这个视图数组把所有的 row trx_id 分成了几种不同的情况。
这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:
如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
如果落在黄色部分,那就包括两种情况
a. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
b. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。
一个数据版本对于一个事务视图来说有三种情况:
版本未提交,不可见;
版本已提交,但是是在视图创建后提交的,不可见;
版本已提交,而且是在视图创建前提交的,可见。
所以当前会话要想知道我能查询到什么数据,他会沿着这条线从后往前找,直到找到符合的。但是对于更新操作就不一样了,是当前读(总是读取已经提交完成的最新版本),更新完之后,将行的row trx_id 设为当前事务的id
除了 update 语句外,select 语句如果加锁,也是当前读
一致读:即读取当前可见的版本。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。