关于事务:真正理解可重复读事务隔离级别

44次阅读

共计 2659 个字符,预计需要花费 7 分钟才能阅读完成。

原创:打码日记(微信公众号 ID:codelogs),欢送分享,转载请保留出处。

事务简介

SQL 规范定义了四种隔离级别,这四种隔离级别别离是:

读未提交(READ UNCOMMITTED):在这种隔离级别下,可能会呈现脏读、不可反复读、幻读问题。
读提交(READ COMMITTED):解决脏读问题。
可反复读(REPEATABLE READ):解决脏读、不可反复读问题。
串行化(SERIALIZABLE):解决脏读、不可反复读、幻读问题。

这里不具体解释脏读、不可反复读、幻读问题这些景象了,介绍事务的文章或书根本都会说得很分明,但请留神,这些都是事务并发运行时可能产生的景象,而不能了解为数据库的 bug!

但我在工作多年后再想到这些常识时,对可反复读行为产生十分大的疑难,如下:

  1. 程序员为什么不把第一次读的数据保留在内存中,第二次重复使用不行吗?为啥要数据库保障两次读出的数据是雷同的(即可反复读),并且读两次数据库会节约更多数据库资源而升高性能。
  2. 另外,每次读出最新的数据有啥不好?读个历史数据有啥用?

既然如此,可反复读到底有什么用?

举个例子

咱们能够考查上面这样的场景,有个金融产品有一个性能,须要查找那些账号余额与账号交易流水对不上的用户,咱们叫到账工作吧,而且要在对账工作运行时,用户交易失常不中断。
比方某账号余额 100 元,该账号有两笔交易记录(+200, -100),这样这个账号就对账失常,但如果程序查问出账号余额 100 元后,这时用户又转出 100 元,咱们再去查问交易记录时,在不同事务隔离级别下会查到不同的后果,如下:

提交读可反复读备注
开始时余额 100,交易记录(+200, -100)
查问到余额 100 元查问到余额 100 元
另一事务收入 100 元,余额缩小为 0,并提交
查问到交易记录(+200, -100, -100)查问到交易记录(+200, -100)
对账失败对账胜利

可见,在提交读场景下,对账失败了,而可反复读场景下对账胜利了,而实际上这个账号的余额与交易记录始终是对齐的。我在 MySQL5.7 亲自验证,后果的确如此。

所以可反复读具体作用是什么呢?
所以可反复读具体作用是什么呢?
所以可反复读具体作用是什么呢?

它实质作用是保障在开启事务后,对数据库所有表数据的查问,查问到的都是雷同的版本,就是开启事务那一刻的版本(在 mysql 中为第一次查问那一刻的版本),而不论它是查问的一个表,还是不同的表,所以可反复读事务级别解决的并不是外表上的不可反复读景象。

可反复读也常常用在数据库备份过程中,因为数据库备份时数据还有可能在一直批改,咱们必定心愿备份整个数据库开始时的那个版本,而不心愿备份的数据有些是之前那个时刻版本的,有些则是之后那个工夫版本的。

这个例子也阐明了另一个问题,即什么时候须要应用事务,刚写代码时咱们常常被告知所有写操作要放到一个事务中,实际上,一些非凡场景,多个读操作也要放到一个事务中。

换角度了解事务

咱们能够不从赃读,不可反复读,幻读这些景象看事务隔离级别,而是从读一致性上来了解,如下:

  1. 未提交读,不解决任何读一致性问题,只保障了事务的写一致性(又称原子性),事务提交后,要么都批改胜利,要么都不胜利。
  2. 提交读,保障其它并发事务的批改要么全可见,要么全不可见,能够了解为 ” 写一致性读 ”,留神断句!” 写一致性 ”、” 读 ”,这是最罕用的事务隔离级别,能够保障业务数据含意的一致性。
    比方用户下单场景,开事务先后写了 order 主表订单数据与 order_item 子表订单中商品数据,如果在两个写两头,有一个未提交读的事务,去读取 order 与 order_item,就会发现只读到了 order 而没有读到 order_item,这给用户看到了,那肯定会吓一跳的,我交钱了后果买了一个空单!尽管用户刷新一下又能够看到残缺数据。
    但如果应用提交读事务隔离级别就不会有这个问题,用户要么查不到任何数据,要么查到残缺数据,这也从侧面阐明了逻辑上有关联的数据批改,肯定要开事务来操作。
  3. 可反复读,保障事务开启或第一次查问那一刻,前面所有对整个数据库所有表的读都是读那一刻的版本,当然包含反复读同一张表,也能够了解为 ” 统一版本读 ”。
  4. 串行化,一般来说解决的是并发上的逻辑谬误,因为此级别逻辑上能够认为所有事务都是串行执行的 (尽管数据库实际上可能会并发执行)。
    比方两个事务先判断数据有没有,没有则插入数据的场景,并发状况下两个事务同时查问,发现没有数据后插入数据,后果插入了两条数据,而应用串行化隔离级别就没有这个问题,这在并发编程中叫竞态条件,所以串行化解决了读写的竞态条件问题。
    当然,这个问题也能够通过增加惟一索引,或应用内部显示加锁的办法来解决。

mysql 可反复读是否解决幻读

在网上,咱们常常会看到两种说法的文章,有的说 mysql 可反复读解决了幻读问题的,也有说没解决的。
这么说也对也不对,具体差别在于以后读取操作是快照读还是以后读,如下:

快照读以后读备注
开始时订单 1 下有两个 order_item,别离 A 和 B
select * from order_item where oid=1(读到 A 和 B)select * from order_item where oid=1(读到 A 和 B)第一次读
另一事务在订单 1 下插入 C 并提交
select * from order_item where oid=1(读到 A 和 B)select * from order_item where oid=1 for update(读到 A、B 和 C)第二次读

下面后果同样在 mysql5.7 下验证通过,咱们称其中的 select xxx for update 为以后读,即读取最新的数据,一般的 select 则是快照读,在 mysql 中 insert、update、delete、select xxx for update 都是以后读。

另外,如果将 select xxx for update 替换为 update order_item set price=199 where oid=1 也同样能够更新到 3 条数据,因为 update 是以后读嘛,乏味的是前面你再应用一般 select 也能够查到 3 条数据,我狐疑是 update 更改了数据的版本为以后事务的版本,导致快照读也能查到,有深刻理解 mysql mvcc 原理的,也能够告知下了解对不对。

所以,mysql 是解决了快照读的幻读问题,没解决以后读的幻读问题,但不论它有没有解决幻读问题,它都是不能代替串行化隔离级别的。

往期内容

Linux 文本命令技巧 (下)
Linux 文本命令技巧(上)
原来 awk 真是神器啊
好用的 parallel 命令
罕用网络命令总结

正文完
 0