我正在尝试实现一个检测帐户是否已经注册的功能。现在做并行请求时,我得到一个死锁。我想我明白为什么会发生这种情况,但我不确定如何解决它。
这是我正在做的事情的简化版本;
START TRANSACTION;
-- check if user has already signed up (returned rows > 0, throw error if so)
SELECT * FROM users WHERE email = 'example@site.com' FOR UPDATE;
-- user has not signed up yet.. create the account.
INSERT INTO users SET ...;
COMMIT;
现在这个本身工作得很好。但是,当两个并行请求发生时,会产生死锁,因为事务都将创建一个FOR UPDATE
锁,这是允许的,因为最初没有帐户注册但没有行要锁定。至少,这是我认为正在发生的事情。如果我说错了请指正。
我很好奇我是如何解决这个问题的,我仍然想检查是否没有一个帐户已经注册,所以我可以向用户显示一条消息。当然,电子邮件有一个unique constraint
,但我不想依赖它,因为auto increment
索引会增加,即使它违反了约束。
我还使用了typeorm,我的代码的一个样本;
public async registerUser(email: string, password: string, displayName?: string) {
const connection = await getConnection();
connection.transaction(async (manager) => {
// First we need to make sure that this email isn't already registered. If
// it has been registered we can throw a simple UserError which will be
// caught by our error handler.
const hasAlreadyRegistered = await this.findUser(email, manager);
if (hasAlreadyRegistered) throw new UserError('Email has already been registered.');
// At last we can create the user, linking him to the previously created
// authentication strategy.
const user = new User();
user.email = email;
user.displayName = displayName || randomBytes(8).toString('hex');
user.strategies = [authentication];
await manager.save(user);
logger.silly('> Created user row.');
return user;
});
}
我通过检查最后的约束错误来解决这个问题(根据@Shadow的建议)。这给我省去了很多麻烦。
try {
await manager.save(user);
} catch (err: any) {
// Check whether or not this entry violates an unique constraint.
if (err.code === 'ER_DUP_ENTRY') {
throw new UserError('Email has already been registered.');
} else throw err;
}