MySQL事务之丢失更新问题

版权声明:本文为 小异常 原创文章,非商用自由转载-保持署名-注明出处,谢谢!
本文网址:https://blog.csdn.net/sun8112133/article/details/89173479








在事务的并发操作中,也就是多个事务同时对同一组数据进行操作时,可能会出现脏读、不可重复读、幻读、丢失更新这四个问题,本篇博客就来为大家讲解 丢失更新 问题。

一、丢失更新概述

丢失更新 就是两个事务在并发下同时进行更新,后一个事务的更新覆盖了前一个事务更新的情况,丢失更新是数据没有保证一致性导致的。比如,事务A 修改了一条记录,事务B 在 事务A 提交的同时也进行了一次修改并且提交。当事务A查询的时候,会发现刚才修改的内容没有被修改,好像丢失了更新。
 
隔离级别 有四种,分别是:读未提交、读已提交、可重复读、序列化。
  读未提交: Read Uncommitted,顾名思义,就是一个事务可以读取另一个未提交事务的数据。最低级别,它存在4个常见问题(脏读、不可重复读、幻读、丢失更新)。
  读已提交: Read Committed,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。 它解决了脏读问题,存在3个常见问题(不可重复读、幻读、丢失更新)。
  可重复读: Repeatable Read,就是在开始读取数据(事务开启)时,不再允许修改操作 。它解决了脏读和不可重复读,还存在2个常见问题(幻读、丢失更新)。
  序列化: Serializable,序列化,或串行化。就是将每个事务按一定的顺序去执行,它将隔离问题全部解决,但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
 
大多数数据库默认的事务隔离级别是 Read Committed,比如 SQL Server , Oracle。但 MySQL 的默认隔离级别是 Repeatable Read。

1、事例

程序员登录到公司的内部员工系统中进行打卡下班,忽然发现自己信息的性别入职时误写成了“女”,于是顺手修改自己的信息,就在这时HR在浏览全公司的名单发现该程序员的部门录入有误,也开始修改该程序员的信息,就在这时,程序员和HR同时点击了提交(HR比程序员稍微慢一点点)。再次刷新,程序员发现明明自己已经提交成功了,为什么自己的性别怎么还是“女”?

2、分析

在这个事例中,程序员事务和HR事务都读取了数据,程序员事务修改后先提交了,接着HR事务也修改了并提交。这时程序员这个事务再次查询发现自己刚才做的修改无效,就好像是刚才的更新丢失了,这是因为HR这个事务修改的数据是在HR事务查询到数据的基础上修改的,所以后一次的更新覆盖了前一次的更新。

3、丢失更新分类

原表

比如,事务A和事务B查询出来的结果如上图,事务A对id为1的数据name字段值改成zhang3,并提交。

1)第一类,提交

事务B将id为1的数据address字段值改成“山西省太原市”,如果提交,那么会造成事务A的更新没有了。数据库的数据为下图:

新表

2)第二类,回滚

事务B将id为1的数据address字段值改成“山西省太原市”,如果回滚,那么会造成事务A的更新没有了。数据库的数据为下图:

原表


二、丢失更新解决方案

1、悲观锁

顾名思义,悲观 是指总是想着不好的事情,而 悲观锁 是指在读取数据的时候总是认为别人会修改它,于是在取数据的时候会对当前数据加一个锁,在结束事务前(提交事务前),不允许其他事务对当前数据进行更改。

要注意 悲观锁乐观锁 都是业务逻辑层次的定义,只是一个解决方案名称而以,不同的设计可能会有不同的实现。在MySQL层常用的 悲观锁 实现方式是加一个 排他锁

排他锁 的解释是:“通过在事务中使用 select xx for update 语句来实现”。排他锁会在当前行加一个行级锁,在当前事务提交前,其他事务无法进行更改操作。”

实际上他的工作原理是这样的,加了排他锁的数据,在释放锁(事务结束)之前其他事务不能再对该数据进行加锁,排他锁之所以能阻止 updatedelete 等更改操作是因为 updatedelete操作会自动加排他锁。

也就是说即使加了 排他锁 也无法阻止 select 操作。而 select XX for update 语法可以对 select 操作加上排他锁。所以为了防止 丢失更新 可以在 select 时加上 for update 加锁,这样就可以阻止其余事务的 select for update(但注意无法阻止 select 操作的)。

比如,事务B如果在查询的时候加了 for update ,如下:

begin;
select * from emp where id = 1 for update;
update emp set name = 'zhang3' where id = 1;
commit;

那么在事务B结束前(提交前),其他事务是无法获得 排他锁 的,从而避免对表中数据的重复更新导致的更新丢失。

2、乐观锁

顾名思义,乐观 是指总是想着美好的事情,而 乐观锁 是指在读取数据的时候总会天真的认为没有人会去修改它,在更改操作的时候再去检查冲突。

相对悲观锁而言,乐观锁 机制采取了更加宽松的加锁机制。悲观锁 大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。

版本表1

乐观锁 的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。需要重新查询再修改。

版本表2

比如,事务A在更改数据的时候,要先看是否匹配版本号,如果匹配成功再修改,失败则不修改,如下:

begin;
select * from emp where id = 1;
update emp set name = 'zhang3' where id = 1 and version = 1;
commit;

有关事务的知识可以参考我之前写的博客《【Spring4.0笔记整理十七】Spring事务详解》【Spring4.0笔记整理十八】Spring事务管理详解


博客中若有不恰当的地方,请您一定要告诉我。前路崎岖,望我们可以互相帮助,并肩前行!



已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页