项目亮点之高并发新增数据唯一
高并发新增数据唯一
悲观锁
在插入前先判断数据是否存在,不存在再进行插入操作,存在返回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锁
思路
存储过程
思路
IF (NOT EXISTS ( SELECT 1 FROH user WHEREname=@userName)
BEGIN
INSERT INTO user ( name ) VALUES〔userName )
END
消息队列
思路
消息队列串行化执行
使用场景对比
实现方式 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
悲观锁 | 实现简单 | 性能差(使用事务) | 补充使用 |
唯一索引 | 性能优异 | 场景有局限(历史表) | 推荐使用 |
redis锁 | 代码解耦 | 性能差(引入外部组件)、复杂性高、维护问题 | 少使用 |
存储过程 | 代码简洁 | 性能差(索引失效)、难以维护 | 不使用 |
消息队列 | 性能优异 | 维护问题(引入外部组件) | 推荐使用 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!