Функция для увеличения и возврата значения (MySQL) - PullRequest
0 голосов
/ 03 января 2019

Предположим, у меня есть строка с идентификатором равным 1 в таблице т.

Если я запускаю следующий sql, я получаю результат, равный 1 (когда столбец c равен 0):

SELECT NEXT_ID(1)

Но, если запустить это, я получу 1 в результате вместо 0 (поскольку в таблице t нет строки с ID = 2):

SELECT NEXT_ID(2)

Функция NEXT_ID:

CREATE FUNCTION NEXT_ID(id INT)
RETURNS VARCHAR(15)
 BEGIN
  DECLARE counter BIGINT DEFAULT 0;
  UPDATE t SET c = (@counter := c +1) WHERE ID = id;
  return @counter;
 END;

Мое намерение здесь состоит в том, чтобы создать счетчик, который увеличивает значение как атомарную операцию. Итак, почему я получаю значение больше 0 для NEXT_ID (2)? Кажется, что переменная счетчика была сохранена в сеансе ...

Безопасно ли это использовать в многопоточном приложении?

1 Ответ

0 голосов
/ 03 января 2019

Если вы DECLARE counter, то вам НЕ следует использовать @counter для ссылки на переменную.В хранимой функции MySQL объявленные переменные не имеют @ сигил.Переменные с символом @ являются определяемыми пользователем переменными .@counter - это переменная, отличная от counter, даже если они имеют одинаковое написание.

Безопасно ли это делать в многопоточном приложении?Конечно, если потоки увеличивают разные строки, используя разные значения id, они не будут конфликтовать (при условии, что id является уникальным ключом таблицы t).

Даже если несколько потоков используют один и тот жеЗначение id, и, следовательно, необходимо увеличить ту же строку в таблице t, что произойдет, если вы попадете туда первым, получите блокировку строки и увеличите ее.Второй поток будет ожидать получения собственной блокировки, пока первый поток не завершит свою транзакцию.Затем второй поток продолжится и увидит увеличенное значение c.

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

Вот почему существует AUTO_INCREMENT, потому что он ненадолго блокирует счетчик для генерации нового значения, ноон немедленно снимает блокировку, не дожидаясь завершения транзакции вызывающей стороны.Это позволяет параллельным потокам продолжать работать и не ждать друг друга.

Нельзя смоделировать поведение AUTO_INCREMENT с UPDATE операциями, которые обязательно находятся в транзакциях.


Re ваши комментарии:

Извините, я забыл, что := не работает с локальными объявленными переменными.Я проверил его и нашел две альтернативы:

Альтернатива 1: не пытайтесь объявить локальную переменную, просто используйте пользовательскую переменную.

CREATE FUNCTION NEXT_ID(id INT)
RETURNS VARCHAR(15)
READS SQL DATA
 BEGIN
  UPDATE t SET c = (@counter := c +1) WHERE ID = id;
  RETURN @counter;
 END

Альтернатива 2: используйтелокальная переменная, но установите LAST_INSERT_ID() в увеличенное значение.Затем SET локальная переменная счетчика с этим значением.

CREATE FUNCTION NEXT_ID(id INT)
RETURNS VARCHAR(15)
READS SQL DATA
 BEGIN
  DECLARE counter BIGINT DEFAULT 0;
  UPDATE t SET c = LAST_INSERT_ID(c +1) WHERE ID = id;
  SET counter = LAST_INSERT_ID();
  RETURN counter;
 END;

Я протестировал обе альтернативы, и они работают.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...