zoukankan      html  css  js  c++  java
  • 并发事务问题之丢失更新

    并发事务问题之丢失更新
      丢失更新:一个事务的更新被另一个事务的更新覆盖了;

    时间点 事务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是否相同;

    悲观锁解决丢失更新
    只需要修改上面代码的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操作是否成功。即查询时和更新时的版本必须一致!

  • 相关阅读:
    操作excel文件的基础工具xlrd/xlwt/xlutils学用
    第12课 OpenGL 显示列表
    第11课 OpenGL 飘动的旗帜
    第10课 OpenGL 3D世界
    第09课 OpenGL 移动图像
    第08课 OpenGL 混合
    第07课 OpenGL 光照和键盘(2)
    第07课 OpenGL 光照和键盘(1)
    第06课 OpenGL 纹理映射
    第05课 OpenGL 3D空间
  • 原文地址:https://www.cnblogs.com/cyf18/p/14315972.html
Copyright © 2011-2022 走看看