高并发情况处理(乐观锁悲观锁)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiao_ma_CSDN/article/details/81842702

当网站到达一定规模时,并发问题也就随之而来,如何高效正确的处理并发问题是每个服务端开发人员所必须掌握的。

处理并发和同步问题

处理并发和同步主要通过锁实现。

  • 一种是代码层次上的,如java中的同步锁,典型的就是同步关键字synchronized。
  • 另外一种是数据库层次上的,比较典型的就是悲观锁和乐观锁。

什么是悲观锁?

悲观锁很悲观,总是认为会发生其他线程修改的情况,在操作之前加锁,当其他线程想要访问数据时,都需要挂起。

悲观锁的实现,依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能 真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。

那么什么是数据库的读写锁?

读锁(共享锁)所有人都只可以读,只有释放锁之后才可以写。
在查询语句中使用...lock in share mode; Mysql会对查询结果中的每一行添加共享锁。
写锁(排他锁)只有锁表的客户可以操作这个表,其他客户都不能读。
在查询语句中使用...for update; Mysql会对查询结果中的每一行添加排他锁。

一个典型的倚赖数据库的悲观锁调用:
select * from user where id=”1” for update
这条 sql 语句锁定了 user 表中所有符合检索条件( id=1 )的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
需要注意 InnoDB 预设的是行级锁,只有明确指定主键,才会执行行级锁(只锁定被选取的数据),否则将执行表锁(锁住整张表)。

需要注意的是for update要放到mysql的事务中,即begin和commit中,否者不起作用。

什么是乐观锁?

乐观锁非常乐观,每次取数据时认为不会有线程对数据修改,在更新时会判断其他线程在这之前有没有修改。
乐观锁,大多是基于数据版本 ( Version )记录机制实现。
具体实现是给数据库表加一个 version 字段,用来记录数据库表被更新的次数,表被修改时,version加一。线程更新数据库时会读到version的值,在提交更新时,当前读到的version的值 >= 数据库中的version值才更新。

乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系 统整体性能表现。 需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局 限性,对于外部系统的更新操作不受控制。Hibernate数据搜索引擎内置了乐观锁实现。

我用Hibernate做了个测试,
User

public class User {
    private Integer id;
    private String userName;
    private int version;
    }

User.hbm.xml

<hibernate-mapping package="one">
    <class name="User"  table="user" >
              <id name="id">
            <generator class="native" />
        </id>
        <!--version标签必须跟在id标签后面-->
        <version column="version" name="version"  />
        <property name="userName" column="userName"/>                
    </class>
</hibernate-mapping>

配置文件hibernate.cfg.xml和UserTest测试类
hibernate.cfg.xml

<session-factory>

        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql:///testbase</property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>
        <!-- SQL dialect -->
        <property name="dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.hbm2ddl.auto">update</property>
        <property name="show_sql">true</property>
        <mapping resource="one/User.hbm.xml" />

    </session-factory>

测试类 Test

public static void main(String[] args) {
        Configuration conf = new Configuration()
                .configure("one/hibernate.cfg.xml");
        SessionFactory sf = conf.buildSessionFactory();
        Session session = sf.openSession();
        Session session2 = sf.openSession();
        User user = (User) session.createQuery(
                " from User user where id=11").uniqueResult();
        User user2 = (User) session2.createQuery(
                " from User user where id=11").uniqueResult();
        System.out.println("user修改前的version"+user.getVersion());
        Transaction tx = session.beginTransaction();//开启事务1
        Transaction tx2 = session2.beginTransaction();//开启事务2
        user.setUserName("111");
        tx.commit();
        System.out.println("user修改后的version"+user.getVersion());
        System.out.println("user2修改前的version"+user2.getVersion());
        user2.setUserName("222");
        tx2.commit();
        System.out.println("user2修改后的version"+user2.getVersion());

    }

这里开启了2个事务,tx更新提交后,version的值已经改变,且大于tx2查到的version,所以tx2事务提交失败。
这里写图片描述

案例:类似于抢票的系统,剩余一张票,1万人同时抢,但是只能一个人抢票成功。(任何高并发网站要考虑的并发读写问题)

要保证所有人都能看到票,最终只能成交一单。

首先我们容易想到和并发相关的几个方案 :

锁同步同步更多指的是应用程序的层面,多个线程进来,只能一个一个的访问,java中指的是syncrinized关键字。锁也有2个层面,一个是java中谈到的对象锁,用于线程同步;另外一个层面是数据库的锁;如果是分布式的系统,显然只能利用数据库端的锁来实现。

假定我们采用了同步机制或者数据库物理锁机制,如何保证1w个人还能同时看到有票,显然会牺牲性能,在高并发网站中是不可取的。使用hibernate后我们提出了另外一个概念:乐观锁、悲观锁(即传统的物理锁);

采用乐观锁即可解决此问题。乐观锁意思是不锁定表的情况下,利用业务的控制来解决并发问题,这样即保证数据的并发可读性又保证保存数据的排他性,保证性能的同时解决了并发带来的脏数据问题。

没有更多推荐了,返回首页