Вот аккуратная проблема блокировки с MariaDB/MySQL.Состояние блокировки таблицы SQL - SELECT, затем INSERT
Сервер повторно собирает многостраничные SMS-сообщения. Сообщения поступают в сегменты. Сегменты с одинаковыми «smsfrom» и «uniqueid» являются частью одного и того же сообщения. Сегменты имеют номер сегмента от 1 до «segmenttotal». Когда все сегменты сообщения пришли, сообщение будет завершено. У нас есть таблица несогласованных сегментов, ожидающих быть собраны следующим образом:
CREATE TABLE frags (
smsfrom TEXT,
uniqueid VARCHAR(32) NOT NULL,
smsbody TEXT,
segmentnum INTEGER NOT NULL,
segmenttotal INTEGER NOT NULL);
Когда новый сегмент приходит, что мы делаем, в сделке,
SELECT ... FROM frags WHERE smsfrom = % AND uniqueid = %;
Это заставляет нас получили все сегменты так далеко. Если новый плюс у них есть все номера сегментов, у нас есть полное сообщение. Мы отправляем сообщение для дальнейшей обработки и удаления фрагментов. Хорошо.
Если не все сегменты уже прибыли, мы делаем INSERT сегмента, который мы только что получили. Autocommit отключен, поэтому обе операции являются частью транзакции. Кстати, двигатель InnoDB.
Состояние гонки. Два сегмента входят одновременно для двухсегментного сообщения и обрабатываются отдельными процессами. Процесс A делает SELECT, ничего не находит. Процесс B выполняет SELECT, ничего не находит. Процесс А вставляет сегмент 1, без проблем. Процесс B вставляет сегмент 2, без проблем. Теперь мы застряли - все сегменты находятся в таблице, но мы этого не замечали. Таким образом, сообщение застряло там навсегда. (На практике мы делаем очистку каждые несколько минут, чтобы удалить старые непревзойденные вещи, но пока не обращаем внимания на это.)
Так что же случилось? SELECT не блокируют строки, потому что они ничего не обнаруживают. Нам нужна блокировка строк в строке, которая еще не существует. Добавление FOR UPDATE к SELECT не помогает; ничего не блокировать. Также LOCK IN SHARE MODE. Даже переход к типу транзакции SERIALIZABLE не помогает, потому что это просто глобальный LOCK IN SHARE MODE.
OK, так что предположим, что мы сначала делаем INSERT, а затем делаем SELECT, чтобы увидеть, есть ли у нас все сегменты. Процесс A делает INSERT равным 1, без проблем. Процесс B имеет вставку 2, без проблем. Процесс A делает SELECT и видит только 1. Процесс B выполняет SELECT и видит только 2. Это повторяемая семантика чтения. Не хорошо.
Подход к грубой силе - это ТАБЛИЦА LOCK, прежде чем делать что-либо из этого. Это должно работать, хотя это раздражает, потому что я занимаюсь транзакциями с участием других таблиц, а LOCK TABLE подразумевает фиксацию.
Выполнение фиксации после каждого INSERT может работать, но я не совсем уверен.
Есть ли более элегантное решение?
Что делать, если вы перенесли чек на другие сегменты после совершения? Необходимо каким-то образом убедиться, что процессы A и B не обрабатывают сообщение. – Greg