在解释脏读、不可重复读和幻读之前,先来说下数据库的事务隔离级别。MySQL 的事务隔离级别(Isolation Level),是指:当多个线程操作数据库时,数据库要负责隔离操作,来保证各个线程在获取数据时的准确性。它分为四个不同的层次,按隔离水平高低排序,读未提交 < 读已提交 < 可重复度 < 串行化。
读未提交(Read uncommitted):隔离级别最低、隔离度最弱,脏读、不可重复读、幻读三种现象都可能发生。所以它基本是理论上的存在,实际项目中没有人用,但性能最高。
读已提交(Read committed):它保证了事务不出现中间状态的数据,所有数据都是已提交且更新的,解决了脏读的问题。但读已提交级别依旧很低,它允许事务间可并发修改数据,所以不保证再次读取时能得到同样的数据,也就是还会存在不可重复读、幻读的可能。
可重复读(Repeatable reads):MySQL InnoDB 引擎的默认隔离级别,保证同一个事务中多次读取数据的一致性,解决脏读和不可重复读,但仍然存在幻读的可能。
可串行化(Serializable):选择“可串行化”意味着读取数据时,需要获取共享读锁;更新数据时,需要获取排他写锁;如果 SQL 使用 WHERE 语句,还会获取区间锁。换句话说,事务 A 操作数据库时,事务 B 只能排队等待,因此性能也最低。
以上就是数据库的事务隔离级别,下面来说下什么是脏读、不可重复读、幻读。
一、脏读: 读到了未提交事务的数据。
假设有 A 和 B 两个事务,在并发情况下,事务 A 先开始读取商品数据表中的数据,然后再执行更新操作,如果此时事务 A 还没有提交更新操作,但恰好事务 B 开始,然后也需要读取商品数据,此时事务 B 查询得到的是刚才事务 A 更新后的数据。
如果接下来事务 A 触发了回滚,那么事务 B 刚才读到的数据就是过时的数据,这种现象就是脏读。
“脏读”如何解决:
脏读的隔离级别是“读未提交”,只有该隔离级别才会出现脏读。
脏读的解决办法是升级事务隔离级别,比如“读已提交”。
二、不可重复读: 事务 A 先读取一条数据,然后执行逻辑的过程中,事务 B 更新了这条数据,事务 A 再读取时,发现数据不匹配,这个现象就是“不可重复读”。
“不可重复读”如何解决:
简单的说是两次读取的数据中间被修改,对应的隔离级别是“读未提交”或“读已提交”。
不可重复读的解决办法就是升级事务隔离级别,比如“可重复度”。
三、幻读: 在一个事务内,同一条查询语句在不同时间段执行,得到不同的结果集。
事务 A 读了一次商品表,得到最后的 ID 是 3,事务 B 也同样读了一次,得到最后 ID 也是 3。接下来事务 A 先插入了一行,然后读了一下最新的 ID 是 4,刚好是前面 ID 3 加上 1,然后事务 B 也插入了一行,接着读了一下最新的 ID 发现是 5,而不是 3 加 1。
使用 ID 做判断或做关键数据时,就会出现问题,这种现象就像是让事务 B 产生了幻觉一样,读取到了一个意想不到的数据,所以叫幻读。当然,不仅仅是新增,删除、修改数据也会发生类似的情况。
“幻读”如何解决:
解决幻读不能升级事务隔离级别到“可串行化”,那样数据库也失去了并发处理能力。
行锁也解决不了幻读,因为即使锁住所有记录,还是阻止不了插入新数据。
解决幻读的办法是锁住记录之间的“间隙”,因此 MySQL InnoDB 引入了新的锁,叫间隙锁(Gap Lock),这块需要使用间隙锁来解决幻读。
,