2016-08-26 5 views
2

Вот аккуратная проблема блокировки с 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 может работать, но я не совсем уверен.

Есть ли более элегантное решение?

+0

Что делать, если вы перенесли чек на другие сегменты после совершения? Необходимо каким-то образом убедиться, что процессы A и B не обрабатывают сообщение. – Greg

ответ

1

Почему не

1) Процесс 1. Вставьте в ваш осколочной таблице. Больше ничего

Вставка .... Commit;

2) Процесс 2 Эта находка полностью многослойную SMS по

выберите smsfrom, уникальный, UniqueID, граф () из группы Frags по smsfrom, уникальный, уникальный, имеющий счетчик () == segmenttotal;

Переместить их в новую таблицу

удалить из фрагов где smsfrom = <> и уникальный = <>;

commit;

+0

Я закончил делать что-то вроде этого - Вставить, зафиксировать, а затем выбрать в одном процессе. Необходимо сделать выбор для ОБНОВЛЕНИЯ, чтобы получить блокировку строк против другого SELECT, или два процесса могли бы найти собранное сообщение, и он будет обрабатываться дважды. –

+0

Очень рад слышать, что помогает ... Я не буду спрашивать, что вы делаете с сообщениями OOB (вне диапазона), которые появляются после того, как вы успешно собрали оригиналы :) - вам может потребоваться их учет или просто удалить все, что составляет более 2 дней - это зависит от ваших бизнес-требований. –

0

Как я уже писал выше, я в конечном итоге делает это:

INSERT ... -- Insert new fragment. 
COMMIT 
SELECT ... FROM frags WHERE smsfrom = % AND uniqueid = % FOR UPDATE; 

Проверьте, если SELECT, возвращается полный набор фрагментов. Если да, соберите и обработайте сообщение, то

DELETE ... FROM FRAGS WHERE smsfrom = % AND uniqueid = %; 

Обязательны COMMIT и FOR UPDATE. COMMIT необходим, чтобы каждый процесс видел любой INSERT из другого процесса. FOR UPDATE необходим для SELECT для блокировки всех фрагментов до тех пор, пока DELETE не будет выполнен. В противном случае два процесса могут видеть полный набор фрагментов в SELECT и собирать и обрабатывать сообщение дважды.

Это удивительно сложно для проблемы с одним столом, но, похоже, работает.