Правильный уровень изоляции для последовательного счетчика

Я работаю над биллинговой системой (код C#, серверная часть MySQL Galera Cluster, механизм хранения InnoDB) и сомневаюсь, как создать действительно уникальный порядковый номер для счетов-фактур.

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

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

Итак, логика, которую я применяю здесь, такова:

  • начать транзакцию
  • создать счет без серийного номера
  • получить последовательный счетчик
  • записать новый счетчик в таблицу
  • обновить счет-фактуру
  • совершить

Если я не ошибаюсь, если какая-то другая транзакция обновит счетчик до того, как текущая транзакция будет завершена, тогда фиксация вызовет исключение, и я смогу повторить операцию, что обеспечит последовательность номеров счетов.

Итак, мой вопрос: какой из них является правильным уровнем изоляции для достижения этой цели? достаточно ли READ_COMMITED или могут возникнуть дубликаты? Или есть лучший подход к этому?


person Gusman    schedule 18.11.2016    source источник
comment
Почему минус?   -  person Gusman    schedule 18.11.2016
comment
Вы имеете в виду кластер NDB? Кластер Галера? Или какой-то другой вкус?   -  person Rick James    schedule 18.11.2016
comment
Это кластер галеры   -  person Gusman    schedule 18.11.2016


Ответы (1)


На самом деле, оба уровня изоляции доставят вам неприятности, если вы не будете осторожны.

Помимо технических различий (например, сколько строк они блокируют), READ COMMITTED и REPEATABLE READ различаются тем, как они обрабатывают следующую ситуацию:

start transaction;
select no from counters where type = 'INVOICE';
-- some other session changes the value and commits it
select no from counters where type = 'INVOICE';

READ COMMITTED даст вам два разных результата, REPEATABLE READ даст вам старое значение для обоих select. Но оба уровня изоляции не помешают никому изменить значение, поэтому вам не нужна ни та, ни другая ситуация.

Важно заблокировать строку, которую вы собираетесь изменить, чтобы никто другой не мог ее изменить:

start transaction;
select no from counters where type = 'INVOICE' for update;
update counters set no = @newvalue where type = 'INVOICE';

или сначала выполните фактическое обновление, если расчет прост:

start transaction;
update counters set no = no + 1 where type = 'INVOICE';
select no from counters where type = 'INVOICE';

Предполагая, что ваша таблица выглядит так (и вы не запрашиваете, например, select max(no) from invoices, чтобы получить последнее число), оба уровня изоляции будут работать. В основном они будут отличаться тем, как (сколько строк) они блокируются. Если у вас есть индекс (в моем примере) type, они будут вести себя точно так же.

Затем решение будет зависеть от остальных ваших запросов. repeatable read обычно является хорошим и безопасным выбором (и по умолчанию по какой-то причине); если вы уменьшите его, вам, возможно, придется больше думать о потенциальных проблемах, но вы можете повысить производительность / уменьшить блокировку.

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

На ваш вопрос, что происходит, когда другая транзакция пытается изменить значение: вторая транзакция будет ждать в точке, где требуется заблокированный ресурс, пока первая не освободит его (обычно, когда он будет готов), и вы получите исключение только тогда, когда достигнут тайм-аут (или обнаружена взаимоблокировка).

person Solarflare    schedule 18.11.2016
comment
Спасибо за столь подробный ответ. Теперь мне это более ясно. Ключ в обновлении, я могу смело увеличивать счетчик перед выбором, мне нравится такой подход. - person Gusman; 18.11.2016
comment
@Gusman - рекомендую внимательно проверить концепцию, возможно, на отдельном столе. В Галере много нюансов, которые могут сбить с толку ваше строгое требование. - person Rick James; 18.11.2016
comment
Серийный номер хранится в изолированной таблице только для этой цели, счета-фактуры находятся в другой таблице, каждое предприятие имеет свою собственную запись в серийной таблице, и я не использую какие-либо суммы или динамические данные для расчета серийного номера, просто простое целое число, поэтому, если блокировка таблицы действительно работает так, как ожидалось, проблем не должно быть, не так ли? - person Gusman; 18.11.2016