MySQL可重复读取好吧,我说,如果发生这种情况是因为在您的应用程序有一个逻辑错误。但是你需要知道并理解发生在MySQL能够避免这个问题。

简而言之,这篇文章的原因是通知你关于可能的陷阱以及如何防止你造成伤害。

让我们先进行一个简短的介绍什么是可重复的读取。给我非常懒惰,我要用现有文档的(很多)MySQL文档。

事务隔离是数据库处理的基础之一。隔离是缩写的我酸;隔离级别设置的回馈都之间的平衡性能和可靠性、一致性和再现性的结果当多个事务同时进行更改和执行查询。

InnoDB提供所有四个事务隔离级别所描述的SQL: 1992标准:读未提交的,读承诺,可重复读,可序列化的。InnoDB的默认隔离级别是可重复读。

  • 可重复读取
    这是InnoDB的默认隔离级别。一致的读取在同一交易快照建立了第一个读过。这意味着,如果你问题几个简单的SELECT语句(不联锁)在同一事务中,这些SELECT语句也彼此一致。
  • 读过承诺
    每个读一致,即使在相同的事务,集和读取自己的新鲜的快照。

和一致的非阻塞读取:

一致的阅读意味着InnoDB使用多版本的给一个查询数据库的快照时间点。查询看到的变化由事务提交之前,并没有更改后或未提交的事务。例外的是查询看到早些时候做出的更改声明在同一事务。这个异常会导致以下异常:如果你更新表中的行,选择看到最新版本的更新的行,但它也可能会看到老版本的任何行。如果其他会话同时更新同一个表,异常意味着你可能会看到表在数据库中不存在。

好的,但实际上这一切是什么意思吗?理解,让我们模拟这种情况:

我有一个店,我决定授予奖金的折扣所选的客户数量:

  • 我的商店有一个活跃的账户
  • 匹配我的个人标准访问奖金

我的应用程序将执行批处理操作更少的交通和无人值守的时刻。这包括重新启动休眠账户,客户要求重新激活。

我们要做的就是看看会发生什么,默认情况下,然后看看我们能做些什么来避免陷阱。

的场景

我将使用三种不同的连接连接到同一个MySQL 8.0.27实例。我唯一修改相关设置的Innodb_timeout我设置为50秒。

  • 会话1将模拟这一过程应该为所选客户激活奖金功能
  • 会话2是一个独立的过程,激活特定的客户列表
  • 会话锁3是用于收集信息

对于这个简单的测试我将使用客户表sakila模式修改如下:

你可以看到我已经添加了奖金activate_bonus字段加上idx_bonus索引。

能够跟踪锁,这些都是由会话的线程id:

锁信息收集:

好的,准备好了吗?让我们开始!

运行…

而下面的步骤可以以一种更完成压缩方式,我更喜欢去做更详细的方法,使其更易于阅读。

首先,让我们设置环境:

然后让看到的客户我们将修改列表:

你可以看到我们有21个客户拟合标准。参与这个练习多少钱?

这个练习花了我~ 242美元。记住这个数字。在这一点上我有什么锁?

答案是没有。

与此同时,我们有其他的过程,需要重新激活的客户:

在这种情况下,这个过程需要重新激活10个用户。

一切美好的,对吧?但在继续之前让我们仔细检查会话1:

完美!我的可重复读还看到相同的快照。让我应用更改:

等等,什么?我清单报告21个条目,但我已经修改31日!如果我检查费用:

好,现在该操作的成本375年美元,而不是242年。在这种情况下,我们讨论的是花生,但猜测可能是如果我们做类似的成千上万的用户。

不管怎样,让我们先:

和取消操作。

所以发生了什么;这是一个错误吗?

不,它不是!它是可重复读取在MySQL中工作。快照是读操作相关,但如果能够编写另一个会话也没有锁的时候读,下一个更新操作将联系表中的任何值匹配的条件。

如上所示,这可能是非常危险的。但只有如果你没有在代码中做正确的事情。

我怎么能阻止这种事的发生呢?

在编码时,希望最好的,更糟糕的是,计划永远!尤其是在处理数据库。这种方法可以节省你的开支晚上试图修复是不可能的。

那么如何预防它?你有两种方式,既简单,但用积极和消极的后果。

  1. 这个简单的clausole添加到select语句:为分享
  2. 添加其他简单clausole select语句:更新
  3. 使用读取提交

解决方案是简单和干净的,没有其他修改代码。但它创建锁,如果您的应用程序锁定敏感,这可能是一个问题。

解决方案两个也容易,但是更多的封装和锁定。

另一方面,解决方案三个不加锁但需要修改代码,它仍然留下了一些空间问题。

让我们看看他们的细节。

解决方案一

让我们重复相同的步骤:

现在我们检查锁(为简便起见我减少一些条目,保存一些为例):

这一次我们可以看到,选择了几个锁在年代(共享)模式。

为简便起见,我跳过报告完全相同的结果在第一运动。

如果我们继续尝试和会话的两个:

在这里,我们走吧!试图改变customer表中的值现在是暂停等待获取锁。如果时间超过Innodb_wait_timeout,然后执行被中断,应用程序必须有一个try - catch机制来重试操作。最后一个是一个最佳实践,您应该已经在您的代码。如果没有,现在将它添加!

在这一点上,会话可以进行并完成操作。后,在决赛之前提交我们可以观察到:

正如你所看到的,我们现在有两个不同的锁类型,共享一个选择的,排它锁定(X)的更新。

解决方案2

在这种情况下,我们只是改变我们的锁上设置的选择。在溶液中我们选择一个共享锁,它允许其他会议最终获得另一个共享锁。这里我们去独占锁,将所有的其他操作。

如果现在我们检查锁(为简便起见我削减了一些条目并保持几为例):

在这种情况下,这种锁是X作为独家,所以其他操作请求锁时必须等待该事务的完成。

它将等待N, N是Innodb_lock_wait_timeout或提交时从会话。

请注意锁请求暂停:

会议两个试图锁定只idx_bonus但不能进行。

一旦会议继续进行,我们有:

现在我更新匹配预期和其他操作。

会议后一个提交:

会话两能够完成,但是在52秒等待会话。说,这个解决方案很好如果你能负担得起锁和等待时间。

解决方案三

在本例中,我们将使用一个不同的隔离模式,允许会话看到会话两所修改。

相同的结果。现在让我们在会话中执行命令二:

似乎是相同的,但是如果我们再次检查会话:

我们现在可以看到所有的31个顾客,也值计算:

这是报告正确的价值观。

在这一点上,我们可以计划一些逻辑流程检查可能的宽容和有流程执行更新操作完成或停止和退出过程。

这个所需要额外的编码和更多的逻辑。

完全诚实,这个解决方案仍有空间一些可能的干扰,但至少允许应用程序了解正在发生的事情。不过,它只应该使用如果你不能有一个更高层次的锁定,但这是另一个故事/条。

结论

编写与RDBMS交互的应用程序时,你必须非常小心在做什么以及如何做。在使用数据便利层和对象关系映射(ORM)一样,似乎让你的生活更轻松,在现实中,你可能会失去控制的几个关键方面应用程序的交互。所以非常小心当选择如何访问您的数据。

关于上面的案例报道中我们可以总结一些缺陷:

  1. 第一和最重要的是,不要过程,可能会干扰同时运行。非常小心当你设计他们时更小心计划执行。
  2. 使用选项如为分享更新在你的select语句。请谨慎使用避免无益的锁,但是使用它们。
  3. 如果你有一个长期的过程,需要修改数据,你需要检查其他进程改变了的状态数据,但与此同时,不能有很长的等待时间锁。然后使用混合,设置授权读取隔离,允许您的应用程序检查,还添加之类的东西为分享更新在select语句,立即在DML和提交之前。可以防止当你敲定写道,并显著减少锁定时间。
  4. 请记住长时间运行的流程和长期运行的事务可以很多痛苦的来源,特别是当使用可重复读。试着当你能把业务分成小块。

最后,在开发,记住,dba是朋友,可以帮助你做你最好的。看起来他们是给你很难,但这是因为他们的观点都是不同的,专注于数据一致性、可用性和持久性。但他们可以帮助你节省很多时间发布的代码之后,特别是当你试图确定为什么出事了。

所以涉及他们很快在设计过程中,使用它们,让他们成为这个过程的一部分。

引用

https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html

https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-model.html

完成2021年Percona开源数据雷竞技下载官网管理软件的调查

你的意见!

订阅
通知的
客人

2评论
最古老的
最新的 大多数投票
内联反馈
查看所有评论
大范

我一直想知道业务场景实现必须使用RR隔离级别

迈克

我们也可以考虑解决方案4:last_update <…?

不可否认这个问题使用也许会改变自己的电子邮件地址在批处理或有其他情况吗?

此外,奖金列的目的是什么,这意味着是多重?或者只是“奖金资格”,但不一定是活跃在这个时间吗?