Параллельные вставки и состояние гонки в MySQL

У меня есть случай, когда я должен ограничить количество строк для каждого пользователя в таблице. Теперь я делаю это с помощью проверки COUNT * FROM table перед вставкой, и если количество равно/больше разрешенного, я выдаю ошибку. Запрос COUNT и INSERT выполняется в одной транзакции. Но при 5000 онлайн-пользователях и 50 000 запросов в минуту у меня есть лишние записи (больше лимита) в таблице. Похоже на состояние гонки на параллельных вставках. Как я могу этого избежать? Может ли кто-нибудь предложить некоторые лучшие практики?


person LONGMAN    schedule 24.09.2018    source источник
comment
Как я могу этого избежать? Может ли кто-нибудь предложить некоторые лучшие практики? Итак, вы выполняете SELECT COUNT(*)..., а затем INSERT ... есть два варианта: 1 перепишите SELECT и INSERT в один запрос, например INSERT INTO table (<columns>) SELECT (<columns|data what you want to insert>) FROM table WHERE .. [GROUP BY ...] [HAVING COUNT(*) < 100] 2 Используйте триггер INSERT BEFORE с SIGNAL.   -  person Raymond Nijland    schedule 24.09.2018
comment
Или выполните блокирующее чтение (выберите ... для обновления).   -  person Shadow    schedule 24.09.2018
comment
@RaymondNijland Первый вариант, который я не получил, как я могу ограничить вставки в зависимости от COUNT в одном запросе? Второй вариант лучше. Но, если вы знаете, возможна ли ошибка состояния гонки между триггером и вставкой?   -  person LONGMAN    schedule 25.09.2018
comment
Блокировка @Shadow при чтении невозможна. Проект имеет высокую нагрузку   -  person LONGMAN    schedule 25.09.2018
comment
Но, если вы знаете, возможна ли ошибка состояния гонки между триггером и вставкой? С помощью триггера вы не можете получить состояние гонки.   -  person Raymond Nijland    schedule 25.09.2018
comment
первый вариант, который я не получил, если вы используете несоответствующий выбор, вы можете избежать вставки, например INSERT INTO table (<columns>) SELECT (<columns|data what you want to insert>) FROM table WHERE 0, обратите внимание на WHERE 0, который также может работать с предложением HAVING с COUNT(*) < 100, если COUNT (*) больше, чем 100, если это то же самое, что и false или 0, что делает его HAVING 0, поэтому ничего не вставляется.   -  person Raymond Nijland    schedule 25.09.2018
comment
@LONGMAN, вам нужно решить, что является большей проблемой: производительность или условия гонки. Блокировка является общедоступным решением в большинстве rdbms для обработки одновременного доступа к данным.   -  person Shadow    schedule 25.09.2018


Ответы (2)


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

person urs_ng    schedule 24.09.2018
comment
В чем разница между вашим и моим текущим решением? Это почти то же самое, и есть вероятность состояния гонки между проверкой и вставкой. - person LONGMAN; 25.09.2018
comment
Опять же, вам придется заблокировать читаемые записи, чтобы включить контроль параллелизма. Преимущество такого решения может заключаться в том, что вам не нужно блокировать записи в таблице транзакций, а только в этой производной таблице. - person Shadow; 25.09.2018

Проблема называется Фантомное чтение. Обычно это можно решить, используя уровень изоляции транзакции Serializable:

https://en.wikipedia.org/wiki/Isolation_(database_systems)

Но это может снизить производительность. Так что, если у вас много вставок, попробуйте и другие варианты из комментариев.

person mroman    schedule 18.10.2018