并发请求引发用户重复创建问题及解决方案咨询
并发场景下重复创建用户问题的解决方案
我们系统通过中间件实现「检查用户是否存在,不存在则创建新用户」的逻辑,但同一新用户的多并发请求会触发重复创建用户的问题。尝试提升事务隔离级别到REPEATABLE READ无效,使用SERIALIZABLE又出现死锁错误,相关代码如下:
async somefunction(usrId: number) { const t = await sequelize.transaction(); try { const user = await usermodel.findOne({ where: { userId: usrId, }, transaction: t, }); if (!user) { const puraUser = await mainusermodel.findOne({ where: { id: usrId, }, transaction: t, }); const dob = puraUser.dob; const age = dob ? UserRepository.calculateAge(new Date(dob)) : 0; try { const dbUser = await usermodel.create( { userId: usrId, age, streakDays: 0, }, { transaction: t, } ); await tblUserSettingsModel.create( { userId: dbUser.id, lowerLimit: defaultUserSettings.lowerLimit, upperLimit: defaultUserSettings.upperLimit, unitId: defaultUserSettings.unitId, }, { transaction: t, } ); await t.commit(); return dbUser; } catch (error) { if (error.name === 'SequelizeUniqueConstraintError') { const dbUser = await tblUserDetailModel.findOne({ where: { userId: usrId }, transaction: t, }); await t.commit(); return dbUser; } else { return null; } } } else { await t.commit(); return user; } } catch (err) { await t.rollback(); logger.error(`CREATE_USER for userId ${usrId}`, err); return null; } }
可行解决方法
1. 唯一约束+重试机制优化
首先确认usermodel的userId字段已设置唯一约束(代码中已捕获SequelizeUniqueConstraintError,说明约束存在,但需确保生效)。并发时第一个请求创建成功后,后续请求会触发唯一约束错误,此时通过重试查询获取已创建的用户,避免事务隔离导致的不可见问题。
修改后代码示例:
async somefunction(usrId: number, retryCount = 3) { const t = await sequelize.transaction(); try { const user = await usermodel.findOne({ where: { userId: usrId }, transaction: t, }); if (!user) { const puraUser = await mainusermodel.findOne({ where: { id: usrId }, transaction: t, }); if (!puraUser) { await t.rollback(); return null; } const dob = puraUser.dob; const age = dob ? UserRepository.calculateAge(new Date(dob)) : 0; const dbUser = await usermodel.create( { userId: usrId, age, streakDays: 0 }, { transaction: t } ); await tblUserSettingsModel.create( { userId: dbUser.id, lowerLimit: defaultUserSettings.lowerLimit, upperLimit: defaultUserSettings.upperLimit, unitId: defaultUserSettings.unitId, }, { transaction: t } ); await t.commit(); return dbUser; } else { await t.commit(); return user; } } catch (err) { await t.rollback(); if (err.name === 'SequelizeUniqueConstraintError' && retryCount > 0) { await new Promise(resolve => setTimeout(resolve, 100)); return this.somefunction(usrId, retryCount - 1); } logger.error(`CREATE_USER for userId ${usrId}`, err); return null; } }
2. 使用行锁(SELECT ... FOR UPDATE)
在查询用户是否存在时添加行锁,强制并发请求排队执行,避免多个请求同时进入创建分支。注意仅针对新用户查询加锁,减少对已有用户查询的性能影响。
修改查询部分:
const user = await usermodel.findOne({ where: { userId: usrId }, transaction: t, lock: t.LOCK.UPDATE, // 启用行锁 });
MySQL中SELECT ... FOR UPDATE会锁定匹配行,若行不存在则不锁,但结合唯一约束可有效避免并发创建。
3. 原子化创建(INSERT ... ON DUPLICATE KEY UPDATE,仅MySQL支持)
利用MySQL原生的INSERT ... ON DUPLICATE KEY UPDATE语法,跳过先查后写的步骤,让数据库原子性处理创建或查询操作,从根源避免并发问题。
示例代码:
async somefunction(usrId: number) { try { const puraUser = await mainusermodel.findOne({ where: { id: usrId } }); if (!puraUser) return null; const dob = puraUser.dob; const age = dob ? UserRepository.calculateAge(new Date(dob)) : 0; // 原子性创建或查询用户,依赖userId的唯一约束 const [dbUser, created] = await usermodel.findOrCreate({ where: { userId: usrId }, defaults: { age, streakDays: 0 }, }); if (created) { await tblUserSettingsModel.create({ userId: dbUser.id, lowerLimit: defaultUserSettings.lowerLimit, upperLimit: defaultUserSettings.upperLimit, unitId: defaultUserSettings.unitId, }); } return dbUser; } catch (err) { logger.error(`CREATE_USER for userId ${usrId}`, err); return null; } }
关键注意事项
- 唯一约束是核心:所有方案都依赖
usermodel.userId的唯一索引/约束,必须确保其生效。 - 缩小事务范围:将主用户查询移到事务外,减少锁持有时间,降低冲突概率。
- 避免高隔离级别滥用:
SERIALIZABLE会导致严重性能问题和死锁,优先用数据库原生原子操作替代。
内容的提问来源于stack exchange,提问作者Muhammad Bilal
相关产品推荐
相关产品推荐

