项目亮点之高并发新增数据唯一

高并发新增数据唯一

悲观锁

在插入前先判断数据是否存在,不存在再进行插入操作,存在返回id

RR事务隔离级别加悲观锁,此事务隔离级别下悲观锁会加临键锁,会阻止其他线程的其他操作(防止幻读),以此实现并发的新增数据唯一

线程并发时,会阻塞其他线程的读写操作,并发事务只会有一个执行成功

思路

//悲观锁必须在整体事务中
set autocommit=0;
//开始事务
begin;
//查询出商品信息,查询的时候必须用for update加锁
select status from items where id=10000 for update;
//根据商品信息生成订单
insert into orders (id,item_id) values (null,10000);
//修改商品status为2
update items set status=2 where id=10000;
//提交事务
commit;

实现方式

  • 字段加普通索引

    悲观锁是加在索引上的,需要加普通索引

    // ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。
    ALTER TABLE table_name ADD INDEX index_name (column_list)
    ALTER TABLE table_name ADD UNIQUE index_name (column_list)
    ALTER TABLE table_name ADD PRIMARY KEY (column_list)
    // 查看索引
    show index from table_name;
    desc table_name;
    
  • 开启事务

    保证事务正确执行,失败回滚事务

    	@Transactional(rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ)
        @Override
        public Long regist(RegistReq req) {
            UserRegistInfo registInfo = userRegistInfoDao.fetchByPhoneForUpdate(req.getPhone());
            if (registInfo != null) {
                log.info(req.getPhone() + "手机号已被注册!");
                throw new RuntimeException(req.getPhone() + "手机号已被注册!");
            } else {
                Date date = new Date();
                log.info("time is: " + date);
                UserRegistInfo info = new UserRegistInfo();
                BeanUtils.copyProperties(req, info);
                info.setPassword(DigestUtils.md5Hex(info.getPassword()));
                info.setCreateTime(date);
                info.setUpdateTime(date);
                userRegistInfoDao.insert(info);
                return info.getId();
            }
        }
    
  • 新增前查询操作加锁

    	<select id="fetchByPhoneForUpdate" resultMap="UserRegistInfo">
            SELECT
            <include refid="Base_Column_List"/>
            from user_regist_info
            WHERE
            phone = #{phone} FOR UPDATE;
        </select>
    

唯一索引

思路

数据库字段级别做到唯一索引,底层就不支持字段重复添加

实现方式

  • 字段加唯一索引

  • 正常插入

    	@Override
        public Long uniqueRegist(RegistReq req) {
            Date date = new Date();
            UserRegistInfo registInfo = new UserRegistInfo();
            BeanUtils.copyProperties(req, registInfo);
            registInfo.setPassword(DigestUtils.md5Hex(registInfo.getPassword()));
            registInfo.setCreateTime(date);
            registInfo.setUpdateTime(date);
            try {
                userRegistInfoDao.insert(registInfo);
            } catch (Exception exception) {
                if (exception.getMessage().indexOf("Duplicate entry") > 0) {
                    registInfo = userRegistInfoDao.getByPhone(req.getPhone());
                    return registInfo.getId();
                } else {
                    log.error("插入错误", exception);
                    throw new RuntimeException(exception.getMessage());
                }
            }
            return registInfo.getId();
        }
    

redis锁

思路

160489343442553

存储过程

思路

IF (NOT EXISTS ( SELECT 1 FROH user WHEREname=@userName)
BEGIN
	INSERT INTO user ( name ) VALUES〔userName )
END

消息队列

思路

消息队列串行化执行

使用场景对比

实现方式 优点 缺点 使用场景
悲观锁 实现简单 性能差(使用事务) 补充使用
唯一索引 性能优异 场景有局限(历史表) 推荐使用
redis锁 代码解耦 性能差(引入外部组件)、复杂性高、维护问题 少使用
存储过程 代码简洁 性能差(索引失效)、难以维护 不使用
消息队列 性能优异 维护问题(引入外部组件) 推荐使用
posted @   Faetbwac  阅读(98)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示