• 并发事务之丢失更新


    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操作是否成功。即查询时和更新时的版本必须一致!

  • 相关阅读:
    jQuery UI (7)Autocomplete 自动补全插件
    jQuery UI (6)Accodion 可折叠面板插件
    jQuery UI (5)Sortable 排序插件
    jQuery UI (4)Jquery UI Selectable 选择插件
    jQuery UI (3)Resiable 调整大小插件
    jQuery UI (2)Droppable 放置插件
    jQuery UI (1)Draggable 拖动插件
    C#(99):Lambda表达式
    C#(99):四种Timer的区别和用法
    C#(99):Reporting Service编程----访问Web服务
  • 原文地址:https://www.cnblogs.com/Prozhu/p/5541980.html
Copyright © 2020-2023  润新知