数据库事务隔离级别

224 阅读4分钟
出来混总是要还的,技术还债第一节:数据库事务隔离级别

一直以来的误解,总认为数据库的事务特性就是,假设同时启动A、B两个数据库事务,应该是互不干扰,在B事务未提交时,A事务时无法获知B的更新。(这个误解的其实就是read committed隔离级别,但它不是全部)

仔细学习才发现,误解有多深,首先事务是有隔离级别,不同的隔离级别,也就决定了A、B事务在何时可以看到对方的更改。另外还涉及到当前读、快照读。


任何支持事务的数据库,其必须遵循ACID原则,原子性、一致性、隔离性、持久性。

其中事务的隔离级别分为:read uncommitted(读未提交)、read committed(读已提交)、repeatable read(可重复读)、serializable(串行化),除了串行化级别,设置其它三个不同的隔离级别,在并发访问数据库时可能会出现脏读、不可重复读、幻读的问题。

下面以mysql为例,分别设置不同的隔离级别来复现脏读、不可重复读、幻读的问题(mysql必须是 InnoDB 引擎才可支持事务)

为模拟应用程序中起长事务,我们启动3个mysql客户端,分别设置客户端不同的事务隔离级别,且均设置
set autocommit = 0,表示不再自动提交事务,需人为手动commit提交,mysql的默认事务隔离级别是
repeatable read
//查看当前事物级别:
SELECT @@tx_isolation;//设置read uncommitted级别:
set session transaction isolation level read uncommitted;

//设置read committed级别:
set session transaction isolation level read committed;

//设置repeatable read级别:
set session transaction isolation level repeatable read;

//设置serializable级别:
set session transaction isolation level serializable;


  • read uncommitted(读未提交)

该级别下,可出现脏读的问题

来自百度百科的释义:脏读又称无效数据的读出,是指在数据库访问中,事务A将某一值修改,然后事务B读取该值,此后A因为某种原因撤销对该值的修改,这就导致了B所读取到的数据是无效的。

下图,在右侧B事务insert在还未commit的情况下,左侧A事务却能查询到该数据。此时,我们如果B事务rollback的话,那么A事务如果继续使用该数据,就是脏数据了。


  • read committed(读已提交)

该级别下,可以解决脏读问题,但可能出现不可重复读问题

来自百度百科的释义:不可重复读,是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。

下图:按照箭头顺序执行,我们发现在read committed级别下,左侧A事务,只有在左侧B事务commit提交后才能看到新插入的数据,但A事务未结束,执行同样的select语句,却得到了不同的结果,这就叫不可重复读。(这个地方也是我之前迷惑的地方,我认为本来就应该这样子,但这是事务级别设置的结果,而非事务仅有如此表现,设置的不同效果也不同)


  • repeatable read(可重复读)

该级别下,可以解决不可重复读问题,但可能出现幻读问题

来自百度百科的释义:幻读(Repeatable Read),当使用可重复读隔离级别时,

如图,在左侧A事务执行相同的select语句,无论右侧B事务执行什么操作,A事务均返回第一次查询的结果,且永远如此,除非A事务结束提交,解决了不可重复读问题。但相应的也带来的幻读的问题,明明已经删除,如果此时A事务再拿该数据做更新会发现并没有效果,就像没更新过一样的幻觉。


关于快照读、当前读,www.cnblogs.com/AlmostWaste…,在这片文章讲的比较详细,我翻看了很多技术博客,大家都是抄来抄去,很多时候忽略的前提条件,造成后续阅读者的困扰。

快照读是基于 MVCC undo log 来实现的,适用于简单 select 语句。而所谓 MVCC 并发版本控制,是靠 readView (事务视图) 来实现的。多个 readView 组成 undo log(回滚日志)。  每一个 sql 查询某条数据时,都是查询最新 readView 的该条数据的值。而对应不同的事务隔离级别,呈现的效果也不同,在read committed下返回当前最新值,在repeatable read永远返回第一次查询的结果。

当前读是基于 临键锁(行锁 + 间歇锁)来实现的,适用于 insert,update,delete, select ... for update, select ... lock in share mode 语句,以及加锁了的 select 语句。

使用当前读的效果就是,在A事务中启动当前读后会阻塞其它事务的非select简单操作,直到A事务提交或回滚