一个事务中 可以查询自己未提交的数据吗_五分钟学习数据库事务(1) - 未提交读

序言

我最近在阅读《数据密集型应用系统设计》这本优秀的书籍,更加深入地了解了数据库事务的相关知识,这系列文章将通过多个例子来阐述各种事务隔离级别的联系与区别,希望能帮助到各位。

什么是事务

你下班回到家,拧开冰冷的可乐,打开计算机准备看剧。当你按下电源开关的时候,计算机会进行一系列的硬件以及软件检查,如果一切都没有问题的话,那么你就可以看喜欢的剧了。但是,这几个步骤中只要有一个出错的话,那么计算机就会无法使用,它可能会尝试不断重启,尝试恢复到初始状态。如果把打开计算机当成一个事务的话,从用户和计算机的角度来看,结果只有成功与失败两种,成功则一切正常,而中间任何一个步骤失败则重启回到初始状态。简单来说,事务就是把一系列的操作当成一个整体,要不就是成功执行(commit),要不就是失败(回滚,放弃)。

为什么需要事务

今天,你到 ATM 存 100 元,

  1. 插入卡片,输入密码后。
  2. 你把 100 元存到自己的账户中。
  3. ATM 先进行验钞,如果是假钞的话就会退出来,结束交易。
  4. 如果是真钞的话,ATM 会更新你的账户余额,类似 Update user set balance = balance + 100 where id = your_id 的 SQL 语句,结束交易。

一切看起来非常正常,不过问题来了,在第 4 步执行更新语句的时候 ATM 机器突然崩溃了,这种情况下你的账户余额会被正确地增加吗?你可能觉得 ATM 崩溃是杞人忧天。但是事实上,有很多情况可以导致 ATM 机器崩溃。

  1. ATM 机器它是一部运行着普通操作系统的机器,而操作系统会因为漏洞或者运行了错误的代码而崩溃。
  2. ATM 机器中运行的交易软件可能会因为更新或者 BUG 而崩溃。
  3. ATM 机器所在的地方可能会断电,即使使用了 UPS,UPS也可能出问题,例如无法正常切换电源。
  4. ATM 数量非常多,总有一些会出错。想象一个拥有一万台计算机的数据中心,即使每个计算机的硬盘平均可以用十年,那么平均每天也有三台计算机需要更换硬盘。

6e650c96db54d326270dfdc074cc7727.png
ATM 崩溃

以存款为例,存款包括验钞,更新账户余额,更新流水等操作,任何一步都可能发生崩溃,如果要考虑每一步崩溃后如何恢复,那么系统就会变得非常复杂。为了简化崩溃恢复的流程,以及防止之后介绍的并发问题,ATM 的软件将存款当成一个事务,也就是原子操作,像开启计算机一样,只有成功或者恢复到初始状态两种结果。如果在操作过程中崩溃了,ATM 可以根据情况继续执行或者回滚已经执行过的操作。相反的,如果你的系统为了追求更高的性能,而且不在乎刚刚提到的问题,那么你完全可以不使用事务或者使用弱事务级别。

未提交读

SQL 中的事务级别分为四种,而里面“最弱而又最高性能”的就是未提交读了,为什么说它是最弱的呢?因为所有并发问题都可能出现在它身上,最高性能呢?越强的事务级别需要越多操作来维护(例如加锁),这系列的操作当然需要耗费额外的资源和时间。特别的是,除了未提交读之外,其他所有事务级别都解决了脏读和脏写两个问题。那么这两个问题是怎么发生的呢?让我们先从脏读开始介绍,我们假设 ATM 把存款中的验钞,账户增加余额,更新账户流水三个步骤当成一个事务,其中任何一个步骤出错则回滚,这里的回滚我们可以理解为反向操作,例如账户增加 100 元的反向操作就是减少 100 元,第一个例子:

  1. 存款的时候,ATM 开始事务,进行验钞,并且判断存进去的是真钞。(此时事务还没有结束)。
  2. 用户查询数据库得到是真钞的响应。
  3. 用户查询余额却发现余额没有对应增加。
  4. ATM 更新账户余额,更新流水,并结束事务。

0931657492b097c092a454187eb446fe.png
脏读 - 例子一

脏读的问题在于,用户能够读取未提交的事务,用户查询数据库的时候知道自己存的是真钞,但是自己的账户余额却没有增加,这明显违背了用户对于 ATM 存款的理解(这是 ATM 不是 A 股)。到这里,你可能会有两个疑问,第一,为什么 ATM 的两次操作之间时间间隔会那么久,ATM 应该在验明真钞之后马上更新账户数据啊?是的,在理想的情况下,数据会被马上更新,但是有许多情况并不是:

  1. ATM 与账户系统在不同的机器中,它们之间需要网络连接,而网络连接可能出现延迟。
  2. 运行更新语句的时候,由于数据库的压力过大,更新语句可能需要进入队列按顺序运行。

第二个疑问,用户 1 在 ATM 更新账户之后再查询的话,那么结果就是正确的嘛,没什么关系,试想第二个例子:

  1. ATM 开始事务,进行验钞,ATM 验钞完毕,增加账户余额 100 元。
  2. 用户查询余额,得到正确的回答 100 元。
  3. ATM 更新账户流水,此时更新失败,事务回滚。
  4. 用户再次查询余额,发现没有任何操作之下,余额从 100 元变成 0 元。

无论用户 1 在之后的什么时间查询,账户余额都为 0 元,而从 100 元到 0 元中间用户什么操作都没有做,这也违背了用户的直觉(骗子也可以利用这个问题,先把钱转账到你的账户,让你有转账成功的错觉,然后再使其回滚)

9c527834e783ee63657411988c5b6c00.png
脏读 - 例子二

相应地,脏写则代表用户更新了未提交事务中的变量值,例如例子 3:

  1. ATM 开始事务,ATM 验钞完毕,增加账户余额 100 元。
  2. 用户查询余额,得到正确的余额 100 元。
  3. 用户取出 100 元,账户余额为 0 元。
  4. ATM 更新流水失败,回滚,但是发现账户余额已经为 0 元。

b71df8264fd76d94609c6ecf4ca3a5d2.png
脏写 - 例子一

除此之外的例子还有许多,脏读和脏写会带给你的系统很多奇怪的问题,而且难以排查。

使用场景

那么未提交读是不是就是一无是处的呢?其实不然,虽然我们还没有提到事务级别是如何实现的,不过你可以猜测我们会使用一些锁来保证资源不会被同时占用,那么使用锁就有代价,其中一个问题就是可能会造成死锁。Stackoverflow 的创始人之一分享了他们在搭建 Stackoverflow 的时候如何解决一个死锁问题,他提到,

I would never recommend using nolock as a general "good for what ails you" snake oil fix for any database deadlocking problems you may have. You should try to diagnose the source of the problem first.
But inpractice adding nolock to queries that you absolutely know are simple, straightforward read-only affairs never seems to lead to problems.

他提出如果在简单的只读查询中使用无锁的机制(尽管会导致脏读),大部分情况下都是没有问题的。也就是说,如果你清楚知道脏读发生对你的系统会产生的最坏影响而你不在乎,或者你发现脏读发生的概率非常低的话,那么你可以使用未提交读来解决死锁的问题。

总结

未提交读在实际应用中使用得并不多,但是了解它的问题可以帮助我们理解其他事务级别。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值