事务隔离级别

事务有哪些特性?

事务有4个特性,即ACID:

  1. 原子性(Atomicity): 事务开始后的所有操作,要么全部成功要么全部失败。
  2. 一致性(Consistency): 事务开始前后数据库的完整性约束没有被破坏,比如:A向B转钱,不可能出现A扣了钱,B没收到钱。
  3. 隔离性(Isolation):多个事务并发访问时,事务之间是隔离的。
  4. 持久性(Durability):事务完成后,事务对数据库的操作被保存在了数据库,不能回滚。

事务有哪几种隔离级别呢?

事务隔离级别从高到低有四种隔离级别,分别是:

  1. 串行化(SERIALIZABLE)
  2. 可重复读(REPEATABLE READ)
  3. 读提交(READ COMMITTED)
  4. 读未提交(READ UNCOMMITTED)

四种隔离级别分别会造成什么问题?

  • 如果数据库采用读未提交(READ UNCOMMITTED)这种隔离级别,会造成脏读。事务还没提交别人就能看到,这样就不能保证你读取到的数据是最终的数据,万一别人把事务回滚了,那就出现了脏数据问题。

  • 读提交(READ COMMITTED)是指一个事务只能读取到其他事务已经提交了的数据,这样就不会出现脏读的问题,但是它会带来不可重复读的问题。比如 A事务 将一个人的姓名从张三改成李四,B事务在A事务提交之前读取到的是张三,但是在A事务提交之后就变成了李四。

  • 可重复读(REPEATABLE READ):可重复读是为了解决READ COMMITTED带来的不可重复读问题,指的是事务不会读取到其他事务对已有数据的修改,即使数据已经提交了。也就是说事务开始读取到的是什么,在事务提交之前的任意时刻,这些数据都一样。虽然解决了不可重复读问题,但是他又会带来幻读的问题。比如A事务将张三修改成李四,B事务再插入一个名叫李四的用户,此时事务A再查找名叫李四的用户会发现多了一条,出现了2个李四,这就是幻读。

  • 串行化(SERIALIZABLE):解决了上面出现的所有问题,但是它效率最差,它将事务的执行变成顺序执行了。

MySQL的默认隔离级别是什么?

Mysql默认的隔离级别是REPEATABLE READ,Oracle则采用的是READ COMMITTED。

我们使用MySQL的时候并没有出现幻读啊,怎么解决的?具体怎么实现的呢?

InnoDB主要是利用锁来解决幻读问题的。

要了解InnoDB怎么解决幻读得先知道InnoDB有哪几种锁。

  • Record Lock:单个行记录上的锁

  • Gap Lock:间隙锁,锁定一个范围,而非记录本身,遵循左开右闭原则

  • Next-Key Lock:结合Gap Lock和Record Lock,锁定一个范围,并且锁定记录本身。主要解决的问题是REPEATABLE READ隔离级别下的幻读。

注意,如果走唯一索引,那么Next-Key Lock会降级为Record Lock,即仅锁住索引本身,而不是范围。也就是说Next-Key Lock前置条件为事务隔离级别为RR且查询的索引走的非唯一索引、主键索引。

下面我们通过具体的例子来模拟上面出现的幻读问题:

CREATE TABLE T (id int ,name varchar(50),f_id int,PRIMARY KEY (id), KEY(f_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8
insert into T SELECT 1,'张三',10;
insert into T SELECT 2,'李四',30;

InnoDB在数据库中会为索引维护一套B+树,用来快速定位行记录。B+索引树是有序的,所以会把这张表的索引分割成几个区间。
image
事务A执行如下语句,需要将张三修改成李四。

select * from t;
update t set name = '李四' where f_id = 10;

这时SQL语句走非唯一索引,因此使用 Next-Key Lock加锁,不仅会给f_10=10的行加上行锁,而且还会给这条记录的两边添加上间隙锁,即(-∞,10]、(10,30]这2个区间都加了间隙锁。
image

此时如果B事务要执行如下语句,都会报错 [Err] 1205 - Lock wait timeout exceeded; try restarting transaction

INSERT INTO T SELECT 3,'王五',10;  -- 满足行锁,执行阻塞
INSERT INTO T SELECT 4,'赵六',8;   -- 满足间隙锁,执行阻塞
INSERT INTO T SELECT 5,'孙七',18;  -- 满足间隙锁,执行阻塞

不仅插入 f_id = 10 的记录需要等待事务A提交,f_id <10、10< f_id <30 的记录也无法完成,而大于等于30的记录则不受影响,这足以解决幻读问题了。

刚刚讲的是f_id 是索引列的情况,那么如果 f_id不是索引列会怎么样呢?

这时候数据库会为整个表加上间隙锁。所以,如果是没有索引的话,不管 f_id 是否大于等于30,都要等待事务A提交才可以成功插入。

原文传送门:面试被吊打系列 - 事务隔离级别

posted @ 2021-04-11 21:49  万般自然  阅读(83)  评论(0编辑  收藏  举报