22.12.2010, 12:59 | #1 |
Участник
|
InventDim::findOrCreate
Решил поделиться интересной особенностью поведения InventDim::findOrCreate() в параллельных транзакциях. Для демонстрации запустите данный job на 2х клиентах. после запуска второго, продолжить первый (набор аналитик должен не существовать). В итоге сгенерится ошибка, что набор аналитик существует. Лечится ситуация с помощью UserConnection для findOrCreate()
X++: static void job(Args _args) { InventDim inventDim; ; ttsbegin; inventDim.InventColorId = 'color1'; inventDim.InventLocationId = 'Loc1'; inventDim.InventSizeId = 'size1'; inventDim = InventDim::findOrCreate(inventDim); Pause; ttscommit; info(inventdim.inventDimId); } |
|
22.12.2010, 13:23 | #2 |
Участник
|
Погодите лечить, давайте с диагнозом определимся.
Может быть это не симптом, а защитная реакция организма? На мой взгляд система всё сделала корректно. На первом кленте вы открываете транзакцию, в которой создаёте новую комбинацию кладских аналитик, но пока транзакция не закрыта запись в БД ещё не произошла (и это правильно). В это время второй клиент хочет воспользоваться такой же комбинацией, не видит её (её ещё нет в БД) и также пытается создать её. В результате как говориться кто первый - того и тапки. Нужно ли такое лечить? Не уверен. |
|
22.12.2010, 14:01 | #3 |
Участник
|
лечить нужно, т.к. не имеет смысла откатывать транзакцию по созданию новой комбинации, т.е. если такая комбинация раз произошла, то вполне возможно, что она еще раз произойдет, значит стоит ее создать и не откатывать, даже если вся транзакция прервалась с какой-либо ошибкой. а вторая транзакция уже воспользуется готовым набором аналитик и не будет ничего создавать
Последний раз редактировалось ice; 22.12.2010 в 14:04. |
|
22.12.2010, 14:10 | #4 |
Участник
|
Присоединяюсь к S.Kustov. Это вовсе не ошибка, а вполне нормальная ситуация.
Кстати, стоит добавить, что UserConnection ничего не "вылечит", поскольку поиск выполняется в режиме "грязного чтения". Т.е. сценарий полностью повторится. Собственно, два разных клиента и так выполняются в разных Connection. Не понятно, как тут поможет тот факт, что добавится еще одно соединение. |
|
22.12.2010, 14:23 | #5 |
Участник
|
|
|
22.12.2010, 14:26 | #6 |
Участник
|
Цитата:
Спорный вопрос. |
|
22.12.2010, 14:31 | #7 |
Участник
|
Цитата:
Приведу пример положительный: происходят две разные долгие операции, вторая операция все это время ждет завершения первой т.к. не может выбрать или создать запись(inventDim). далее первая операция завершается. и тутже завершается вторая с ошибкой, хотя могло этого не произойти Последний раз редактировалось ice; 22.12.2010 в 14:39. |
|
22.12.2010, 14:57 | #8 |
Участник
|
Не просто увеличение количества строк, а появление мусора в InventDim. Например в той же транзакции могут выделяется номера партий, серийные номера ещё какие-нибудь складские аналитики, создание которых будет отменено при откате транзакции, а в InventDim останутся несуществующие значения. На сколько это критично не возьмусь судить.
|
|
22.12.2010, 15:13 | #9 |
Участник
|
При чем здесь транзакция? Вы обратили внимание, что команда InventDim::find() выполняется БЕЗ второго параметра. Т.е. идет режим "грязного чтения" и факт наличия или отсутствия транзакции вообще никак не влияет. Да даже если и нет "грязного чтения". Все равно результат будет тот же самый.
Два пользователя "одновременно" начали создание одной и той же складской аналитики. Первый пользователь создал новую запись. А что должно произойти со вторым? Вполне логично, что его должно выбросить с сообщением об ошибке. И какая разница, будет выполняться поиск в одной транзакции или в другой? Результат будет одинаковый - прерывание обработки. И ничем, никоим образом, здесь не поможет факт поиска в другой транзакции. Теоретически, здесь могло бы помочь "зацикливание". Т.е. если произошла ошибка InventDim::FindOrCreate(), то делаем повторную попытку его выполнения. Но, здесь будут свои особенности. Еще раз повторюсь. UserConnection - не поможет. Вам просто повезло. Стечение обстоятельств. Видимо, из-за дополнительной задержки, потребовавшейся на создание соединения поиск у второго "пользователя" произошел тогда, когда первый "пользователь" уже создал запись. |
|
22.12.2010, 15:21 | #10 |
Участник
|
Для лучшего понимания процесса - по шагам:
- Первый пользователь выполнил InventDim::find() - ничего не нашел - Второй пользователь выполнил InventDim::find() - ничего не нашел - Первый пользователь создал запись inventDim - Второй пользователь попытался создать запись InventDim - получил сообщение об ошибке И чем здесь поможет выполнение операции поиска/создания в другом соединении? Да ничем! |
|
22.12.2010, 15:43 | #11 |
Участник
|
Цитата:
Сообщение от Владимир Максимов
Для лучшего понимания процесса - по шагам:
- Первый пользователь выполнил InventDim::find() - ничего не нашел - Второй пользователь выполнил InventDim::find() - ничего не нашел - Первый пользователь создал запись inventDim - Второй пользователь попытался создать запись InventDim - получил сообщение об ошибке И чем здесь поможет выполнение операции поиска/создания в другом соединении? Да ничем! |
|
22.12.2010, 16:10 | #12 |
Участник
|
На сколько я себе это представляю, модель процесса немного другая:
1. Первый пользователь открыл транзакцию 2. Первый пользователь выполнил InventDim::find() - ничего не нашел 3. Первый пользователь "создал" запись inventDim 4. Второй пользователь открыл транзакцию 5. Второй пользователь выполнил InventDim::find() - ничего не нашел (т.к. первый ещё не закрыл транзакцию) 6. Второй пользователь "создал" запись inventDim 7. Первый пользователь закрыл транзакцию (только тут запись пошла в БД) 8. Второй пользователь попытался закрыть транзакцию - получил сообщение об ошибке По идее, если на шаге 7 первым транзакцию закроет второй пользователь, то ошибку получит первый. |
|
22.12.2010, 16:31 | #13 |
Участник
|
Цитата:
Сообщение от S.Kuskov
На сколько я себе это представляю, модель процесса немного другая:
1. Первый пользователь открыл транзакцию 2. Первый пользователь выполнил InventDim::find() - ничего не нашел 3. Первый пользователь "создал" запись inventDim 4. Второй пользователь открыл транзакцию 5. Второй пользователь выполнил InventDim::find() - ничего не нашел (т.к. первый ещё не закрыл транзакцию) 6. Второй пользователь "создал" запись inventDim 7. Первый пользователь закрыл транзакцию (только тут запись пошла в БД) 8. Второй пользователь попытался закрыть транзакцию - получил сообщение об ошибке По идее, если на шаге 7 первым транзакцию закроет второй пользователь, то ошибку получит первый. |
|
|
За это сообщение автора поблагодарили: S.Kuskov (1). |
22.12.2010, 16:46 | #14 |
Участник
|
AX 3.0. Транзакция второго пользователя ждет окончания выполнения транзакции первого. После завершения первой транзакции, вторая нормально отрабатывает без всяких ошибок.
AX 4.0 Транзакция второго пользователя не ждет завершения первой транзакции. Отрабатывает в любой последовательности и без ошибок. AX 2009 нет под рукой, проверить не могу. Но там, кстати, появился X++: Exception::DuplicateKeyException Может у Вас включены какие-то дополнительные хинты для SQL? У меня ошибка не появляется. |
|
22.12.2010, 17:11 | #15 |
Участник
|
Цитата:
Сообщение от _scorp_
AX 3.0. Транзакция второго пользователя ждет окончания выполнения транзакции первого. После завершения первой транзакции, вторая нормально отрабатывает без всяких ошибок.
AX 4.0 Транзакция второго пользователя не ждет завершения первой транзакции. Отрабатывает в любой последовательности и без ошибок. AX 2009 нет под рукой, проверить не могу. Но там, кстати, появился X++: Exception::DuplicateKeyException Может у Вас включены какие-то дополнительные хинты для SQL? У меня ошибка не появляется. PS у меня ax2009 sql2008 |
|
22.12.2010, 17:19 | #16 |
Участник
|
|
|
22.12.2010, 17:37 | #17 |
Участник
|
Доделать также, как с генерированием numbersequence.
с выделением нового номера. |
|
22.12.2010, 20:20 | #18 |
Участник
|
Цитата:
Сообщение от ice
все у вас правильно, только момент между 1 и 3 очень маленький и вероятность вклинивания 2 очень мала. а вот вероятность попадания в описанную мной ситуацию очень большая
Если подходить с точки зрения стороннего наблюдателя, то будет казаться, что выполняются следующие процессы 1. Оба пользователя "одновременно" открыли транзакцию 2. Оба пользователя "одновременно" выполнили поиск аналитики. 3. Оба пользователя "одновременно" ничего не нашли 4. Оба пользователя "одновременно" попытались создать новую запись. Один создал, другой "завис" до завершении транзакции первого пользователя 5. Первый пользователь завершил транзакцию, второй попытался создать запись - получил ошибку Но поскольку "одновременность" - это иллюзия, создаваемая компьютером, то логично разбить процесс на понятные последовательные этапы. Что я и сделал в самом начале. Однако во всей этой схеме "тонкий" момент - это момент поиска. Поиск осуществляется командой select inventDim where ... И вот тут-то и возникает вопрос настроек "по умолчанию". Причем настроек именно AOS. Если настройки по умолчанию предполагают, что подобный запрос уйдет на сервер с хинтами грязного чтения, то 100% получим ошибку. И дополнительное соединение здесь никак не поможет. Поскольку в этом дополнительном соединении запрос также пойдет с хинатми грязного чтения Если же настройки по умолчанию предполагают, что подобный запрос уйдет на сервер без дополнительных хинтов, то, если до завершения поиска одним пользователем, другой уже успел внести изменение, первый пользователь "повиснет" в ожидании завершения транзакции второго пользователя. В этом случае сценарий будет похож на то, что описал S.Kuskov, но с одной существенной поправкой: 1. Оба пользователя "одновременно" открыли транзакцию 2. Оба пользователя "одновременно" начали поиск 3. Один из них закончил поиск первым и успел создать запись 4. Тогда другой "подвиснет" в процессе поиска и его поиск останется не завершенным до окончания транзакции первого пользователя 5. Первый пользователь завершил транзакцию 6. Второй пользователь продолжил поиск и нашел запись созданную первым 7. Второй пользователь также успешно завершил транзакцию Конфликта вообще не будет. И это-то как раз наиболее вероятный сценарий. Поскольку полная "одновременность" завершения поиска двумя пользователями - это, скорее, исключительная ситуация. И дополнительное соединение здесь опять роли не играет, поскольку также будет ждать завершения транзакции первого пользователя. А отсюда возвращаемся к вопросу Цитата:
Сообщение от _scorp_
Может у Вас включены какие-то дополнительные хинты для SQL?
PS: Кстати, "случайно" режим кеширования таблицы InventDim не меняли? |
|
22.12.2010, 21:36 | #19 |
Участник
|
Цитата:
Цель - получить следующую последовательность действий: 1. Первый пользователь открыл основную транзакцию 2. ...Первый пользователь открыл фоновую транзакцию в отдельном соединении 3. ...Первый пользователь в отдельном соединении выполнил findOrCreate 4. ...Первый пользователь подтвердил фоновую транзакцию (новая комбинация складских аналитик окончательно запостилась в таблицу) 5. Второй пользователь открыл свою транзакцию 6. Второй пользователь выполнил InventDim::find() - нашёл запись! 7. Второй пользователь подтвердил свою транзакцию 8. Первый пользователь подтвердил свою основную транзакцию Последний раз редактировалось S.Kuskov; 22.12.2010 в 22:05. |
|
22.12.2010, 22:03 | #20 |
Участник
|
Цитата:
Сообщение от S.Kuskov
Дополнительное соединение призвано изменить поведение не второго клиента (для него действительно принципиально ничего не измениться), а первого! Суть в том что если первый клиент не будет откладывать вставку нового значения в InventDim до завершения своей транзакции (используя дополнительное соединение это возможно, верно?), то к моменту когда второй клиент будет искать заданную комбинацию - он её найдёт(!). При этом основная транзакция первого клиента может быть ещё не завершена. Ещё раз, дополнительное соединение используется для выноса действий по вставке значения из основной транзакции (длинной) в отдельную (короткую).
|
|