并发事务之丢失更新

Posted prozhu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发事务之丢失更新相关的知识,希望对你有一定的参考价值。

7 并发事务问题之丢失更新

  丢失更新:一个事务的更新被另一个事务的更新覆盖了;

时间点

事务1

事务2

t1?

开始事务

?

t2?

?

开始事务

t3?

查询pid=p1的记录结果为[pid=p1,pname=zhangSan,age=23,sex=male]

?

t4

?

查询pid=p1的记录结果为[pid=p1,pname=zhangSan,age=23,sex=male]

t5?

修改age=24,其它保留原值,即:

update person set pname=‘zhangSan‘,

age=24,sex=‘male‘ where pid=‘p1‘;

?

t6?

提交事务

?

t7?

?

修改sex=female,其它保留原值

update person set pname=‘zhangSan‘,

age=23,sex=‘female‘ where pid=‘p1‘;

t8

?

提交事务

?

事务2覆盖了事务1的更新操作。结果为:[pid=p1,pname=zhangSan,age=23,sex=female]。因为事务2没有在事务1的基础上进行更新,而是在自己的查询基础上进行更新。

?

public class Demo1 {

????private static Connection getConnection() throws Exception {

????????String driverClassName = "com.mysql.jdbc.Driver";

????????String url = "jdbc:mysql://localhost:3306/day12?useUnicode=true&characterEncoding=utf8";

????????String username = "root";

????????String password = "123";

?

????????Class.forName(driverClassName);

????????return DriverManager.getConnection(url, username, password);

????}

?

????public Person load(Connection con, String pid) throws Exception {

????????String sql = "select * from t_person where pid=?";

????????PreparedStatement pstmt = con.prepareStatement(sql);

????????pstmt.setString(1, pid);

????????ResultSet rs = pstmt.executeQuery();

????????if (rs.next()) {

????????????return new Person(rs.getString(1), rs.getString(2), rs.getInt(3),

????????????????????rs.getString(4));

????????}

????????return null;

????}

????

????public void update(Connection con, Person p) throws Exception {

????????String sql = "update t_person set pname=?, age=?, gender=? where pid=?";

????????PreparedStatement pstmt = con.prepareStatement(sql);

????????pstmt.setString(1, p.getPname());

????????pstmt.setInt(2, p.getAge());

????????pstmt.setString(3, p.getGender());

????????pstmt.setString(4, p.getPid());

????????

????????pstmt.executeUpdate();

????}

????

????@Test

????public void fun1() throws Exception {

????????Connection con = getConnection();

????????con.setAutoCommit(false);

???????? ?

//[pid=p1,pname=zs,age=24,gender=male]

????????Person p = load(con, "p1");

????????p.setAge(42);//断点

????????update(con, p);

????????

????????con.commit();

????}

????@Test

????public void fun2() throws Exception {

????????Connection con = getConnection();

????????con.setAutoCommit(false);

//[pid=p1,pname=zs,age=24,gender=male]

????????Person p = load(con, "p1");

????????p.setGender("female");//断点

????????update(con, p);

????????

????????con.commit();

????}

}

?

处理丢失更新:

  • 悲观锁:在查询时给事务上排他锁,这可以让另一个事务在查询时等待前一个事务解锁后才能执行;
  • 乐观锁:给表添加一个字段,表示版本,例如添加version字段,比较查询到的version与当前vesion是否相同;

?

7.1 悲观锁解决丢失更新

只需要修改上面代码的load()方法中select语句即可:

select * from t_person where pid=? for update

?

????public Person load(Connection con, String pid) throws Exception {

????????String sql = "select * from t_person where pid=? for update";

????????PreparedStatement pstmt = con.prepareStatement(sql);

????????pstmt.setString(1, pid);

????????ResultSet rs = pstmt.executeQuery();

????????if (rs.next()) {

????????????return new Person(rs.getString(1), rs.getString(2), rs.getInt(3),

????????????????????rs.getString(4));

????????}

????????return null;

????}

?

悲观锁:悲观的思想,认为丢失更新问题总会出现,在select语句中添加for update为事务添加排他锁,这会让其他事务等待当前事务结束后才能访问。当然,其他事物的select语句中也要加上for update语句才会等待;

悲观锁的性能低!

?

7.2 乐观锁

乐观锁与数据库锁机制无关;

我们需要修改t_person表,为其添加一个字段表示当前记录的版本。例如给t_person表添加version字段,默认值为1。

当事务查询记录时得到version=1,再执行update时需要比较当前version的值是否与查询到的version相同,决定update是否执行成功。如果update成功,还要把version的值加1。

????public void update(Connection con, Person p) throws Exception {

????????String sql = "update t_person set pname=?, age=?, gender=?, version=version+1 where pid=? and version=?";

????????PreparedStatement pstmt = con.prepareStatement(sql);

????????pstmt.setString(1, p.getPname());

????????pstmt.setInt(2, p.getAge());

????????pstmt.setString(3, p.getGender());

????????pstmt.setString(4, p.getPid());

????????pstmt.setInt(5, p.getVersion());

????????

????????pstmt.executeUpdate();

????}

?

  • 事务1:查询时得到version=1;
  • 事务2:查询时得到version=1;
  • 事务1:执行update时因为version没有改变,所以update执行成功,update不只修改了age=42,还修改了version=2;
  • 事务2:执行update语句时version已经为2,而查询时的version为1,所以update执行失败;

?

乐观锁:与数据库锁机制无关,乐观的思想,认为丢失更新不是总出现;通过给表添加版本字段来决定update操作是否成功。即查询时和更新时的版本必须一致!

以上是关于并发事务之丢失更新的主要内容,如果未能解决你的问题,请参考以下文章

事务并发之隔离级别

Mysql事务并发问题与隔离级别深入解析

乐观锁与悲观锁——解决并发问题

乐观锁与悲观锁——解决并发问题(转)

转事务并发的问题

一步步学习并发:了解并发是如何发生的