悲观锁-利用数据库设计多活高并发自增ID

现在有一个需求,在不使用redis的前提下,设计一个可以多节点共同访问的自增ID系统

首先我想到的就是秒杀抢单的时候,我们常用的悲观锁原则,不然,在高并发场景下,利用数据库来做计数是无法保证数据安全的,也就是常说的,锁不住。

表设计(先设计一个最简单的表:当前需求是每天生成一套计数器):

CREATE TABLE `test_key` (
  `key` int(6) DEFAULT '0',
  `data_time` varchar(16) NOT NULL,
  PRIMARY KEY (`data_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

现在来梳理一下流程:

1.查询当前标号(锁表):

SELECT `key` + 1 AS `key` FROM test_key WHERE data_time = "2021-12-15" FOR UPDATE

 2.将当前标号加一:

UPDATE test_key SET `key` = `key` + 1 WHERE data_time = "2021-12-15"

3.如果查询到没有当天的数据,则新建一条数据:

INSERT INTO test_key (data_time) VALUES ("2021-12-16")

 现在,来看在SpringBoot项目中是怎么实现这个流程的

dao层:

@Mapper
public interface KeyMapper {
	@Select("SELECT `key` + 1 AS `key` FROM p1project.test_key WHERE data_time = #{dataTime} FOR UPDATE")
	Integer getKeyByDataTime(@Param("dataTime")String dataTime);

	@Update("UPDATE p1project.test_key SET `key` = `key` + 1 WHERE data_time = #{dataTime}")
	void updateKey(@Param("dataTime")String dataTime);

	@Insert("INSERT INTO p1project.test_key (data_time) VALUES (#{dataTime})")
	void insertKey(@Param("dataTime")String dataTime);
}

service层(隔离机制:Isolation.READ_COMMITTED【读已提交】):

@Service
public class KeyService {

	@Resource
	private KeyMapper keyMapper;

	@Transactional(isolation = Isolation.READ_COMMITTED)
	public int getKeyByData() throws Exception{
		String dataTime = DateUtil.formatDate(new DateTime());
		Integer key = keyMapper.getKeyByDataTime(dataTime);
		if (key == null){
			keyMapper.insertKey(dataTime);
			return 0;
		} else {
			keyMapper.updateKey(dataTime);
			return key;
		}
	}

}

controller层:

/**
     * 获取行业列表
     * @param response
     * @param request
     * @throws IOException
     */
    @GetMapping("/test")
    @ResponseBody
    public void test(HttpServletResponse response, HttpServletRequest request) throws IOException {
        int key = 0;
        try {
            key = keyService.getKeyByData();
        } catch (Exception e) {
            //避免高并发场景下的新增唯一键冲突,做一次try-catch,保证事务完整性的前提下重试一次,取得计数
            try {
                key = keyService.getKeyByData();
            } catch (Exception exception) {
                log.error("处理异常",e);
            }
        }
        JSONObject result = SystemUtils.responseOk(0, "success", key);
        response.setContentType("json/application;charset=UTF-8");
        response.getWriter().print(result.toString());
    }

 正如注释中写的:避免高并发场景下的新增唯一键冲突,做一次try-catch,保证事务完整性的前提下重试一次,取得计数。当不存在唯一键的时候,如果不做try-catch的话,会导致同时插入唯一键冲突。

可以开一个多线程模拟测试一下:

public class DemoRun implements Runnable{
    //重写的是Runnable接口的run()
    @Override
    public void run() {
        int outTime = 1000*60*10;
        String result = HttpUtil.get("http://localhost:8084/test",outTime);
        System.out.println(result);
    }

    public static void main(String[] args) {
        for (int run=0;run<5;run++){
            Thread thread1 = new Thread(new DemoRun());
            Thread thread2 = new Thread(new DemoRun());
            Thread thread3 = new Thread(new DemoRun());
            Thread thread4 = new Thread(new DemoRun());
            Thread thread5 = new Thread(new DemoRun());
            Thread thread6 = new Thread(new DemoRun());
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
            thread6.start();
        }
    }
}

测试结果如下

悲观锁-利用数据库设计多活高并发自增ID_第1张图片

可见,开启多线程访问并不会导致重复ID产生,在并发的情况下能保证数据的可靠性

你可能感兴趣的:(解决方案,java,数据库开发,高并发编程,后端,mysql)