Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
проблема тривиальна, а решение… не очень
Вообще-то, стоит отметить, что решение такой задачи с помощью блокировок на уровне кода делает такое решение не масштабируемым.
Я, кстати, так и не понял, чем вас не устраивает стандартная конструкция lock () {}, что вы реализовали свою?
«Заведем табличку CurrentThreadLocks» — и будем мучаться с кодом для синхронизации еще и этой таблички :-)
И ваш CurrentThreadLocks может обрести человеческое лицо несмотря на то, что он наверняка дублирует уже существующий класс.
легче отлаживать и искать баги
, ну и соответственно нет тех проблем про которые статья.
Конкретно в вашем примере вместо одного конекта и запроса к БД получается 2.
Мне кажется конкретно в вашем случае транзакции в коде только добавляют проблем.А какие есть альтернативы?
легче отлаживать и искать баги
, ну и соответственно нет тех проблем про которые статья.
А зачем парсеру результат метода Get?А что в этом странного? Workflow примерно следующий:
какая вообще задача перед вами стоит? Что это за система такая, в которой множество потоков может паралельно создавать обьекты с одинаковыми именами в базе, при чем с такой частотой, что это превращается в серьезную проблему? Ну если это не секрет, конечно.Да ничего особенного. Просто есть движок, которому на вход подается очень много входных данных для параллельной обработки. Обработка в том числе включает и операции типа GetOrCreate.
Да и операция GetOrCreate сама по себе выглядит как-то странно, никогда с такой не встречался. Нарушается принцип single-responsibility — операции чтения должны только читать (при этом операции записи могут возвращать результат своей работы, но не наоборот).Странно? По-моему обычная сервисная операция (на самом деле это и есть сервисная операция, принимающая в виде зависимости два репозитория; выше приведен упрощенный пример). Не думаю, что GetOrCreate нарушает SRP: я не вижу у него более одной причины для изменения.
В общем, у меня такое впечатление, что у вас проблемы на более высоком уровне — на уровне самой архитектуры, поетому такая внешне простая задача и решается столь извращенным способом (в силу своей нестандартности).Я думал об этом… но архитектурного решения описанной проблемы не нашел: все упирается в транзакции — хоть ты тресни.
Просто есть движок, которому на вход подается очень много входных данных для параллельной обработкиСферический в вакууме? В реальной жизни входные данные откуда-то беруться. Я пока что не вижу ни одной причины, по которой вообще нужно вставлять что-то в базу данных при первом запросе обьекта по имени. Если весь сыр бор из-за того, что вам нужно генерировать идентификатор, то выше я уже написал: используйте GUID.
Делать блокировки в клиентском коде для разруливания обращений к базе — плохо. Например потому что с той же таблицей может работать и другой код, которому тоже нужно как-то синхронизироваться. Ну или если два процесса будет работать.Согласен. Выше уже предлагались варианты переноса локов в базу данных. Но ведь проблема-то реально не в этом (неужели я бы не сделал распределенную блокировку, если бы нужно было работать из нескольких процессов?). Наверное, это я в статье неправильно расставил акценты, раз половина комментариев посвящены не основной проблеме.
Кстати такой подход к блокировке называется Double-checked locking и часто встречается в статьях про синглтоны.Doble-checked locking (кстати, для синглтонов в чистом виде он не всегда работает из-за кэширования памяти) это прекрасно, но выполнять приведенные вами запросы вы будете в рамках одной транзакции (не видя незакоммиченные изменения других транзакций), а значит поставленную задачу это не решает.
Ну во-первых эта ситуация будет только в случае если две транзакции добавляют одну и ту же несуществующую еще фирму. В других случаях блокировки не будет.Я в самом начале поста обозначил, что в виду очень высокой степени параллелизации для меня эта проблема существенна. На вход системе подается большой объем однотипных данных и особенно на начальном этапе очень много таких конфликтов: данные новые и каждая транзакция, не обнаружив необходимой записи, пытается их добавить.
Есть еще и третий вариант. Как реализовать нужно думать, но тема такая: мы даем другим транзакциям использовать новую запись сразу после добавления, а потом отслеживаем ситуацию когда ни одна из транзакций не завершилась успехом, в этом случае запись добавиться не должна. Но тут решение будет сильно завязано на логику приложения.Ммм… Непонятно :)
В остальном — да, мои решения делают тоже самое что и твои, но на уровне базы. Что на мой взгляд правильнее — меньше проблем будет в дальнейшем.Концептуально лучше — я и не спорю. Но реальность вносит свои коррективы
Хорошая практика в этом случае — замерить результаты в самом простом варианте и сравнивать их с оптимизированным. Иначе может получиться лишняя сложность на пустом месте. Я бы на твоем месте попробовал мой вариант, т.к. совершенно не факт что твои доводы насчет производительности верны.Так я замеряю. Второй подход быстрее первого, и я отписал об этом в статье. Ваш код я не протестировал по следующей причине: для его реализации мне нужно существенно переработать код, но при этом я на 99.999% уверен, что выигрыша в производительности не будет. Ему просто неоткуда взяться.
Только недавно мне рассказывали товарищи базисты, как они клиентский код распараллелили, а получилось только медленнее.Нет, ну это уже другая история. Я пробую разные варианты, тестирую их и последовательно выбираю оптимальный.
смысле что база обнаруживает дедлоки и прямо о них сообщает исключением.Это да. Но это уже зависит от программиста: локи нужно расставлять с умом.
Даже если в коде будет сделан таймаут, по нему нельзя будет узнать причину — дедлок ли это был, или это админ на сервере порнуху копировал.А мне кажется, в большинстве случаев реально пофиг, почему транзакция свалилась. Свалилась — пробуем еще раз.
Первая транзакция просто не увидит добавленную строкуВ текущей реализации ей и не нужно видеть: данные ей возвращают независимые транзакции. Но даже если бы я захотел сделать выборку внутри основной транзакции (после работы независимых транзакций), она бы увидела эти данные: ведь я первый раз их считываю уже после того, как они были закоммичены независимой транзакцией.
Но даже если бы я захотел сделать выборку внутри основной транзакции (после работы независимых транзакций), она бы увидела эти данные: ведь я первый раз их считываю уже после того, как они были закоммичены независимой транзакцией.
When a transaction is using the serializable level, a SELECT query only sees data committed before the transaction began; it never sees either uncommitted data or changes committed during transaction execution by concurrent transactions.
All consistent reads within the same transaction read the snapshot established by the first read.А ведь это логично. В этом и заключается отличие Repeatable Red и Serializable от Read Committed.
In REPEATABLE READ mode, the range would not be locked, allowing the record to be inserted and the second execution of Query 1 to return the new row in its results.
Транзакции и многопоточный доступ к базе данных