ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JDBC_15_悲观锁和乐观锁

2021-04-17 21:34:52  阅读:137  来源: 互联网

标签:事务 JDBC 15 数据库 并发 connection 悲观 数据


悲观锁和乐观锁

  • 并发控制

    • 当程序中可能出现并发操作的情况时,就需要保证在并发操作的情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和某个用户单独操作时的结果是一样的。这种手段就叫做并发控制。并发控制的目的是
      保证一个用户的操作不会对另一个用户的操作结果产生不合理的影响。

    • 如果没有做好并发控制,就可能导致数据脏读、幻读和不可重复读等问题。

    • 并发控制,一般都和数据库管理系统(DBMS)有关。在 DBMS 中的并发控制的任务,是为了确保在多个事务同时存取数据库中的同一数据时,不破坏事务的隔离性、一致性和数据库的统一性。

    • 实现并发控制的主要手段大致可以分为乐观并发控制 和 悲观并发控制两种。

  • 乐观锁和悲观锁

    • 无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。

    • 其实不仅仅是关系型数据库系统中才有乐观锁和悲观锁的概念,像 hibernate、tair、memcache 等都有类似的概念。所以,不应该拿乐观锁、悲观锁和其他的数据库锁等进行对比。

    • 乐观锁比较适用于读多写少的情况(多读场景),悲观锁比较适用于写多读少的情况(多写场景)。

    • 悲观锁(行级锁)(Pessimistic Lock)

      当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制.

      简单来说就是指某些数据被锁住了,事务需要这些数据的话,就必须排队获取,在当前事务结束之前,别的事务根本修改不了锁住的数据,不支持并发操作。

      语句:SELECT ENAME ,JOB,SAL FROM EMP WHERE JOB='MANAGER' FOR UPDATE; 在语句后面加了for update就产生了行级锁,所查询出的数据就会被锁住。

      在传统的关系型数据库多使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作数据之前先上锁。Java 里面的同步 synchronized 关键字的实现。

      悲观锁主要分为共享锁和排他锁:

       > 共享锁【shared locks】又称为读锁,简称S锁。顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
      
       > 排他锁【exclusive locks】又称为写锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务可以对数据行读取和修改。
      
    • 乐观锁(Optimistic Locking)

      乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提
      高程序的吞吐量。

      乐观锁机制采取了更加宽松的加锁机制。乐观锁是相对悲观锁而言,也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。

      要使用悲观锁,必须关闭 MySQL 数据库的自动提交属性。因为 MySQL 默认使用 autocommit 模式,也就是说,当执行一个更新操作后,MySQL 会立刻将结果进行提交。 (sql语句:set autocommit=0)

      乐观锁支持并发操作,事务不需要排队,只需要获取版本号,查看事务获取数据时和提交时的version号是否一致,一致就提交,不一致就不提交。

      乐观锁的实现:

         1. CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
      
         2. 版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。例如,某条数据version号是1.1, 事务1,和事务2,同时获取同该条数据,事务1先进行了修改,修改后查看版本号还是1.1,然后提交事务,这时事务2 
            修改了数据,然后再次查看版本号,获取到了version为1.2,和开始获取的不一致,那么事务2就会放弃这次的修改操作,回滚,不提交事务了。
      
  • 代码模拟演示悲观锁 (事务1查询数据并使用 for update锁住所查询出的数据, 事务2尝试对事物1锁住的数据进行修改)

    

    import com.shige.JDBC.Utils.DBUtil;

    import java.sql.*;

    /**
     * 事物1
     * 演示悲观锁(行级锁)
     * 该事务进行查询操作,并使用悲观锁,锁住相关数据
     */
    public class JDBCTest10 {
        public static void main(String[] args) throws SQLException {

             //创建数据库连接所需要的对象‘
            Connection connection=null;
            PreparedStatement preparedStatement=null;
            ResultSet resultSet=null;
            try {
                //获取连接
                connection=DBUtil.getConnection();

                // 关闭事务自动提交
                connection.setAutoCommit(false);


                //获取数据库预编译对象
                String sql="SELECT ENAME ,JOB,SAL FROM EMP WHERE JOB=? FOR UPDATE";
                preparedStatement=connection.prepareStatement(sql);


                //给占位符传值
                preparedStatement.setString(1,"MANAGER");

                //执行SQL
                resultSet=preparedStatement.executeQuery();

               
               //处理查询结果集
                while(resultSet.next()){
                    System.out.print(resultSet.getString("ENAME")+"  ");
                    System.out.print(resultSet.getString("JOB")+"  ");
                    System.out.println(resultSet.getString("SAL")+"  ");
                }
                
            } catch (SQLException e) {
                //回滚事务
                if(connection!=null){
                    connection.rollback();
                }
                e.printStackTrace();
            }finally {
                //释放资源
                DBUtil.close(connection,preparedStatement,resultSet);
            }
        }
    }





   // 这是事物2  对事物1锁住的数据进行修改操作。

  
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;

    /**
     * 演示悲观锁(行级锁)
     * 该事务进行修改被上个事务锁住的数据
     */

    public class JDBCTest11 {
        public static void main(String[] args) throws SQLException {

            //创建数据库连接所需要的对象‘
            Connection connection=null;
            PreparedStatement preparedStatement=null;
            int count=0;

            try {
                //获取连接
                connection= DBUtil.getConnection();

                // 关闭事务自动提交
                 connection.setAutoCommit(false);

                //获取数据库预编译对象
                String sql="UPDATE EMP SET SAL=SAL*2 WHERE JOB=?";
                preparedStatement=connection.prepareStatement(sql);

                //给占位符传值
                preparedStatement.setString(1,"MANAGER");

                //执行SQL
                count=preparedStatement.executeUpdate();

               //输出
                System.out.println(count==3?"修改成功":"修改失败");

                //手动提交事务
                 connection.commit();

            } catch (SQLException e) {

                //回滚事务
                if(connection!=null){
                    connection.rollback();
                }
                e.printStackTrace();
            }finally {
                //释放资源
                DBUtil.close(connection,preparedStatement,null);
            }
        }
    }

标签:事务,JDBC,15,数据库,并发,connection,悲观,数据
来源: https://www.cnblogs.com/szqengr/p/14672197.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有