Блокировка MySQL при регистрации учетной записи

#mysql #sql #node.js #typeorm

Вопрос:

Я пытаюсь реализовать функцию, которая определяет, была ли уже зарегистрирована учетная запись. Теперь при выполнении параллельных запросов я попадаю в тупик. Я думаю, что понимаю, почему это происходит, но я не уверен, как это решить.

Вот упрощенная версия того, что я делаю;

 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;
    });
  }
 

Комментарии:

1. Если значение автоматического приращения увеличивается, то что с того? Автоинкремент должен предоставлять уникальный идентификатор, он не обязательно должен быть непрерывным!

2. @Тень Да. Может быть, вы правы, просто кажется неправильным иметь пробелы между идентификаторами. Я посмотрю на то, чтобы просто проверить наличие ошибки ограничения, не совсем уверенный, как бы я сделал это в typeorm, поскольку я думаю, что имя ограничения является случайным.

Ответ №1:

Я решил эту проблему, просто проверив в конце ошибку ограничения (по предложению @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;
  }