聊聊mysql的事务

今天来聊聊事务的四大特性以及其实现原理,需结合之前写的mysql是如何实现mvcc的来理解,因为大多数的实现都是基于mvcc的,理论介绍完后会通过实例来演示mvcc又是如何实现这些隔离级别的

事务的四大特性

  • 原子性
    事务的执行要么全部成功要么全部失败,成功的时候直接提交就好,某些环节失败的时候是需要回滚的,这就要用到回滚日志(undolog)根据当前记录的回滚指针去undolog定位历史记录了;
  • 一致性
    事务的其他特性就是为了保证一致性的
  • 隔离性
    两个事务之间的执行应该是隔离开的,各自造成的数据改变不会相互影响,有四个隔离级别下面会一一解释
  • 持久性
    事务提交后持久化到硬盘,通过redo log实现

隔离级别

说明:下面的概念解释假设有两个事务A、B处于活跃状态

  • 读未提交(READ-UNCOMMITTED)
    事务B读到了事务A新增但未提交的数据;这种事务未提交就被其他事务读取到的现象叫做脏读;而在实际生产中这是毫无意义的,所以几乎不会被用到
  • 读已提交(READ-UNCOMMITTED)
    是oracle的默认隔离级别;事务B读到了事务A修改了并且已经提交了的数据;分两种情况,一是事务A修改了数据导致B在第二次读的时候与第一次读到的结果不一样,这种现象叫做不可重复读;二是事务A新增了数据导致B在第二次查询时候与第一次查询时读到的记录数不一致,这种现状叫做幻读;即RC级别下可能会出现幻读和不可重复读的现象
  • 可重复读(REPEATABLE-READ)
    是mysql的默认隔离级别;事务A对数据的操作不影响事务B,即事务B在事务A修改数据前后查的数据是一致的;这种级别看似不会出现问题,但是当当前读和快照读混用的时候也是会出现幻读或者不可重复读的问题的,下面我会演示这一现象;而mysql是通过mvcc+间隙锁来解决这一问题的
  • 串行(SERIALIZABLE)
    事务串行执行,效率低但不会出现脏读、幻读、不可重复读的现象

小总结:综上所述,幻读可以理解成相同条件下事务前后读取到的记录数不一样,而不可重复读可理解成事务前后读取都的内容不一样,记录数可以一样;

不同隔离级别下的实操

数据准备

  • 创建一个测试表test
CREATE TABLE test (
  id int NOT NULL AUTO_INCREMENT,
  name varchar(255) DEFAULT NULL,
  age int DEFAULT NULL,
  PRIMARY KEY (id)
)
  • 插入原始测试数据
insert into test values(10,'zhangsan',19);
insert into test values(20,'lisi',21);
insert into test values(30,'wangwu',21);

可重复读(RR)隔离级别下测试

  • 1.打开两个命令行窗口并关闭事务的自动提交;然后分别在两个窗口手动开启事务;如图:

  • 2.在A事务窗口执行查询操作:select * from test where age = 21;结果和预期一致符合条件的是两条数据,如图:

  • 3.在B事务窗口执行插入操作insert into test values(40,'zhaoliu',21);后提交事务并进行查询select * from test where age = 21;此时查询到的是三条记录,如图:

  • 4.在事务A窗口重新执行select * from test where age = 21;发现得到的结果与第一次一致还是两条

分析:
综上可以发现,事务A在事务周期内在事务B对表做了新增数据的情况下两次查询结果仍然一致,没有出现幻读、不可重复读的情况,是因为mysql的默认隔离级别是RR(可重复读),而正常情况下RR是可以避免幻读和不可重复读的情况的,除非在事务周期内混用了当前读和快照读,下面来演示RR级别下出现幻读的情况;

  • 5.在上面的基础上我们在事务A的查询语句基础上新增一个for update语句:select * from test where age = 21 for update;此时我们就会发现记录数变成了3条(如图),即出现了幻读的情况,因为类似for update、delete等语句是会使用当前读的,而之前的查询语句用的是快照读,这就出现了快照读和当前读混用的现象从而出现了幻读

读已提交(RC)隔离界别下的测试

步骤与上面几乎一致,只是需要在开启事务之前将隔离级别设置成读已提交(RC),如图:

接下来重复上面的步骤:

  • 1.关闭事务的自动提交;手动开启两个事务,这里就不上图了
  • 2.事务A执行select * from test where age = 21;得到的结果是三条(上面的测试新增了一条数据),如图:
  • 3.事务B执行insert into test values(50,'qianqi',21);后提交事务并进行查询select * from test where age = 21;此时查询到的是四条记录,如图:
  • 4.在事务A窗口重新执行select * from test where age = 21;此时我们发现得到结果却是4条记录与第一次查询的结果不一致,这就出现了幻读的现象,如图:

思考:为什么在测试步骤和数据不变的情况下不同的隔离级别下查询的结果不一致呢?
这是因为两种隔离级别下生成readView的时机不一样,mysql默认的可重复读级别下是在事务中的第一次查询时生成的readview,后续在没有当前读的情况下事务内所有的查询都是基于第一次查询生成的readview的快照读;而读已提交的隔离级别下在事务内的每次查询都会生成一个readview,所以造成了两种不同的现象;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章