MySQL乐观锁 实现秒杀案例,增加version字段来判断(jfinal框架)

发布时间:2018-08-18作者:laosun阅读(2920)

MySQL乐观锁

    MySQL乐观锁和悲观锁的主要区别在于:

    悲观锁在查询的时候就认为别人会和我抢,我查询的时候就先锁起来。

    乐观锁在查询的时候认为别人不会和我抢,我在更新的时候判断一下就可以了。

    所以如果在高并发的情况下,博主认为乐观锁并不适合做这种业务,因为是先查询,导致数据库有很多的更新失败,处理异常再次执行的情况,相当于再次查询执行业务处理。

    MySQL悲观锁的实现情况请参考这篇文章。 MySQL悲观锁 select for update实现秒杀案例

    这次博主的例子还是在Jfinal框架中。想了解Jfinal框架的朋友可以去 这个地址看看:

    项目下载地址:Jfinal 3.4        

    本次测试例子下载 :jfinal_demo_for_maven.zip


    不要在乎代码质量,博主仅仅只是模拟这种请求。


    主要源码:

    package com.demo.index;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import com.jfinal.core.Controller;
    import com.jfinal.plugin.activerecord.Db;
    
    /**
     * 秒杀/MySQL 乐观锁实现方式
     * 
     * @author sun
     */
    public class SecKillForOLController extends Controller {
    	
    	public static int goodsTotalNum = 100;
    
    	public void index() {
    		//每一次新的请求都会模拟抢购100件商品,所以先恢复商品的数量,和版本号,这个可以不要,也可以手动去更新数据库
    		Db.update("update t_goods set num = ?, version = 1 where id = 1", goodsTotalNum);
    		
    		SecKillOL sk = new SecKillOL();
    		// 模拟120个人抢购。 模拟每60人分别在不同的服务器进行发起请求,这样我们的synchronized锁才能体现出来
    		//如果我们只new一个SecKillOl,那么相当于synchronized锁了。MySQL乐观锁无法体现出来。
    		for (int i = 1; i <= 60; i++) {
    			new Thread(sk, i + " 号人").start();
    		}
    		SecKillOL sk2 = new SecKillOL();
    		for (int i = 61; i <= 120; i++) {
    			new Thread(sk2, i + " 号人").start();
    		}
    		renderHtml("<center><h2>抢购成功!</h2></center>");
    	}
    }
    
    /**
     * 这里我们只是演示数据库悲观锁的实现方式,至于事务等其他的都不添加了。
     * 乐观锁本来就不太适合秒杀等业务处理,因为乐观的本质就是: 我很乐观,我先查询,我不认为别人会和我抢。
     * @author sun
     */
    class SecKillOL implements Runnable {
    
    	@Override
    	public void run() {
    		synchronized (this) {
    			//如果version字段对不上,回滚进行再一次提交,我这里就设置轮询十次,如果还是没有对上,则相当于抢购失败
    			//这块的乐观锁异常处理根据实际情况来做,比如你也可以增加while(true)直到成功为止。
    			for(int i=0;i<10;i++){
    				Connection conn = null;
    				PreparedStatement ps = null;
    				try {
    					conn = Db.use().getConfig().getConnection();
    					ps = conn.prepareStatement("select * from t_goods where id = 1");
    					ResultSet rs = ps.executeQuery();
    					Integer num = 0;
    					Integer version = null;
    					while (rs.next()) {
    						num = rs.getInt("num");
    						version = rs.getInt("version");
    					}
    					if (num > 0) {
    						PreparedStatement ps2 = conn.prepareStatement("update t_goods set num=num-1, version=version+1 where id = 1 and version = ?");
    						ps2.setInt(1, version);
    						int result = ps2.executeUpdate();
    						if(result>0){
    							System.out.println(Thread.currentThread().getName() + " 抢到了第" + (SecKillForOLController.goodsTotalNum - num + 1) + "商品");
    							break;
    						}else{
    							System.err.println(Thread.currentThread().getName() + " 有人抢到了我前边,导致我无法进行更新,我再尝试一遍");
    						}
    					}else{
    						System.err.println(Thread.currentThread().getName() + " 太悲剧了,没抢到商品...");
    						break;
    					}
    				} catch (SQLException e1) {
    					e1.printStackTrace();
    				} finally {
    					try {
    						ps.close();
    					} catch (SQLException e) {}
    					try {
    						conn.close();
    					} catch (SQLException e) {}
    				}
    			}
    		}
    	}
    }
    代码里有些jfinal的东西,看不懂的不要紧,只要知道是操作sql就行。

    打印结果:

    1 号人 抢到了第1商品
    55 号人 抢到了第2商品
    61 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    54 号人 抢到了第3商品
    61 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    61 号人 抢到了第4商品
    53 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    120 号人 抢到了第5商品
    53 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    119 号人 抢到了第6商品
    53 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    117 号人 抢到了第7商品
    53 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    118 号人 抢到了第8商品
    53 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    53 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    116 号人 抢到了第9商品
    115 号人 抢到了第10商品
    53 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    ......
    11 号人 抢到了第98商品
    10 号人 抢到了第99商品
    66 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    9 号人 有人抢到了我前边,导致我无法进行更新,我再尝试一遍
    66 号人 抢到了第100商品
    9 号人 太悲剧了,没抢到商品...
    69 号人 太悲剧了,没抢到商品...
    8 号人 太悲剧了,没抢到商品...
    67 号人 太悲剧了,没抢到商品...
    7 号人 太悲剧了,没抢到商品...
    65 号人 太悲剧了,没抢到商品...
    6 号人 太悲剧了,没抢到商品...
    68 号人 太悲剧了,没抢到商品...
    64 号人 太悲剧了,没抢到商品...
    63 号人 太悲剧了,没抢到商品...
    5 号人 太悲剧了,没抢到商品...
    62 号人 太悲剧了,没抢到商品...
    4 号人 太悲剧了,没抢到商品...
    3 号人 太悲剧了,没抢到商品...
    2 号人 太悲剧了,没抢到商品...
    60 号人 太悲剧了,没抢到商品...
    59 号人 太悲剧了,没抢到商品...
    58 号人 太悲剧了,没抢到商品...
    57 号人 太悲剧了,没抢到商品...
    56 号人 太悲剧了,没抢到商品...

    t_goods表结构:

    CREATE TABLE `d_sunjs_test`.`t_goods`  (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `num` int(11) NULL DEFAULT NULL,
      `version` int(11) NULL DEFAULT 1 COMMENT '乐观锁',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

    乐观锁的实现方式是通常在表里增加一列version或者时间戳之类的:

    如果A、B两个用户抢购同一个商品(只有一件),这两个用户如果都进行了查询(如果version的初始值是1),那么两个用户都拿到了这个值,在更新的时候,如果B先更新的,更新语句类似: update t_goods set num=num-1,version=version+1 where id = 1 and version = 上边的version值:1; 那么这条语句更新肯定没问题。如果A再更新,那么where后边的条件就不满足了,所以导致更新失败。 这就是所谓的乐观锁。


    下一篇文章 博主将演示如何使用redis分布式锁来进行秒杀抢购

7 +1

版权声明

 Java  源码  多线程

 请文明留言

0 条评论