|
|
#1 |
|
Участник
|
Простой код, но UpdateConflict, почему?
Мне попался код, который очень простой и не кажется ошибочным на первый взгляд, но он выдавал updateConflict. Я хочу лучше понять механику процесса, буду благодарна объяснению
X++: ttsbegin while select forupdate purchline where purchid = 'MyCurrentPONumber' { ... if (some condition) { ttsbegin; purchline.FieldA = 'aa'; purchline.update(); ttscommit; } } ttscommit Я также понимаю, что этот update(в versioning) меняет recversion и ,соответственно, он уже не тот на линиях, что был выбран в "while select forupdate purchline" Но я тогда не понимаю, 1) как можно в одной транйакции(то есть, либо все проапдейчены линии, либо нет) сделать апдейт в всех линий какого-нибудь одного заказа за закупку? Это же типичный сценарий, но versioning может его сломать... 2) есть ли в коде выше какие-то очевидные ошибки (если бы вы не знали, что там проблема есть). вроде, классика делать ttsbegin while select forupdate purchline where purchid = 'MyCurrentPONumber' { purchline.update(); } ttscommit 3)Ну, и вообще, это же одна транзакция тут (внешняя, что до while select), то есть lock должен защищать от одновременного апдейста разными транцакциями. В рамках одной все должно быть ок, разве нет? Последний раз редактировалось Lankey; 07.01.2026 в 22:19. |
|
|
|
|
#2 |
|
Administrator
|
Ну давайте разберём по порядку.
1. Технологию (платформу, ядро) создают одни, а затем логику (приложение, код на Х++) пишут другие. Т.е. те люди, которые предполагали, что будет цикл по перебору строк в одной транзакции - не предполагали, что другие будут писать логику на purchline.update(). А те, кто писали логику на purchline.update() ... видимо вообще не думали - делали, как им проще закодить.2. В одной транзакции сделать апдейт всех строк можно. Вопрос лишь ценой каких усилий и каких ограничений Вы же всегда можете написать код видаttsbgin; purchTable = PurchTable::find('MyCurrentPONumber', true); purchTable.FieldA = 'aa'; purchTable.update(); purchTable = PurchTable::find('MyCurrentPONumber', true); purchTable.FieldB = 'bb'; purchTable.update(); ttsbegin; Ну и для PurchLine - аналогично. Другое дело, что это создаст нагрузку на БД при массовом обновлении и могут появиться блокировки опять-таки при обработке большого количества данных, но... сделать же можно ![]() 3. В отдельных случаях (когда логика на update небольшая) - можно вынести в код эту логику и написать .doupdate(). Да, это нарушит принцип "не надо дублировать код", но... работать будет. А логику можно вынести в отдельный метод и его просто вызывать в разных ситуациях и тогда проблемы дублирования кода уйдут. 4. В рамках одной транзакции всё должно быть ок, но при условии, что каждый следующий update() получает актуальный курсор. Т.е. если мы выбрали purchLine, затем изменили у него FieldA и сделали update(), то перед обновлением FieldB - нужно, чтобы в переменной purchLine содержался бы курсор с уже обновленным FieldA, а не тот, который был бы на момент начала транзакции (кстати, именно для этого на таблице есть метод reread()). 5. И еще надо учесть такое понятие, как пессимистическая и оптимистическая блокировки. В оптимистической - да, именно так, как Вы описали, и так у многих табличек работает по умолчанию. Однако, если курсор второй раз выбран под пессимистическую блокировку - то вторая выборка на обновление будет "висеть" до завершения транзакции по первому update(). Пессимистическая блокировка - это когда в коде пишется select pessimisticlock purchLine. Либо select forupdate purchLine, а на purchLine установлено свойство optimisticConcurrency = No (такие конструкции встречаются в коде у таблички InventSum). В Вашем случае конечно такого нет, но в общем случае (не с purchLine) момент с блокировками нужно учитывать
__________________
Возможно сделать все. Вопрос времени |
|
|
|
|
#3 |
|
Участник
|
Спасибо, но, мне кажется, я плохо объяснила
1) Стандарт вызывает в purchLine.update() апдейт др строк той же закупки в VersioningPurchaseOrder.archivePurchLine(). Поэтому, после purchLine.update() первой строки, когда мы переходим в цикле ко второй той же закупки, то она уже проапдейчена и буфер из while select forupdate purchline where purchid = 'MyCurrentPONumber' имеет "старый" recVersion Тк закупки - одна из основных функциональностей, а уж апдейт всех строк закупок в транзакции - типичный сценарий, я не понимаю, как может быть , что описанный код с простым while select forupdate purchline не работает. Как бы вы написали апдейт всех строк одной закупки в одной транзакции , если бы вам понадобилось? 2) while select forupdate purchline должно накладывать пессимистичную блокировку , но это, имхо, тут не важно, тк все происходит в контексте одной и той же транзакции. Разве нет? Последний раз редактировалось Lankey; 08.01.2026 в 22:36. |
|
|
|
|
#4 |
|
Участник
|
Соглашусь с sukhanchik
попробуйте перед X++: if (some condition)X++: purchline.reread(); Еще я бы куда-нибудь, например в Set запоминал RecId обработанной записи и в цикле дополнительно проверял что запись не содержится в списке обработанных, а если содержится то пропускал бы. В подобных вашему примеру есть вероятность что запись может повторно в выборку цикла попасть и будет бесконечный цикл. Такие случаи (с зацикливанием) обсуждались на форуме ранее. |
|
|
|
|
#5 |
|
Участник
|
Цитата:
Сообщение от Logger
Соглашусь с sukhanchik
попробуйте перед X++: if (some condition)X++: purchline.reread(); Еще я бы куда-нибудь, например в Set запоминал RecId обработанной записи и в цикле дополнительно проверял что запись не содержится в списке обработанных, а если содержится то пропускал бы. В подобных вашему примеру есть вероятность что запись может повторно в выборку цикла попасть и будет бесконечный цикл. Такие случаи (с зацикливанием) обсуждались на форуме ранее. |
|
|
|
|
#6 |
|
Участник
|
Вопрос уже поднимался
![]() отладка AX2012 PurchLine update conflict ?? Я для себя нашел такой "костыль" X++: ttsbegin; while select forUpdate PurchLine where ... { if (purchLine.IsModified) { purchLine.reread(); } purchLine.FieldXXX = ...; purchLine.update(); } ttsCommit; Если интересует чисто технический момент "как такое может быть", то дело в том, что "за раз" Axapta забирает несколько записей. По умолчанию, если не ошибаюсь, по 2 записи. По этой причине, собственно, конфликт и возникает. Цикл взял вторую запись из буфера, но она уже изменена при обработке первой записи
__________________
- Может, я как-то неправильно живу?! - Отчего же? Правильно. Только зря... |
|
|
|
| За это сообщение автора поблагодарили: S.Kuskov (10), Lankey (1). | |
|
|
#7 |
|
Участник
|
Спасибо, Владимир!
Вторая ссылка - это как раз мой случай. Потрясающе, что есть какой-то стандарт разработки(как писать апдейт) . А микрософт его, получается, "хакнул". Причём не на какой-то третьесортной таблице, а одной из основных. Как аксапта работает без постоянных конфликтов на этой таблице, если там такая бомба? Стандарт вещде reread делает? Плюс , потрясающе, что в d365 все ещё та же проблема, что, судя по ссылкам, была в AX2012 Последний раз редактировалось Lankey; 12.01.2026 в 17:23. |
|
|
|
|
#8 |
|
Участник
|
Посмотрите, как работает отмена покупки, метод PurchCancel\updatePurchTable()
X++: if (!purchTable.selectForUpdate()) { purchTable = PurchTable::findRecId(purchTable.RecId, true); } if (purchTable.ChangeRequestRequired && purchTable.DocumentState >= VersioningDocumentState::Approved) { VersioningPurchaseOrder::newPurchaseOrder(purchTable).createChangeRequest(); } else if (!VersioningPurchaseOrder::newPurchaseOrder(purchTable).isLastVersionArchived() && purchTable.DocumentState == VersioningDocumentState::Confirmed) { // Force archiving to avoid it during line cancellation as that would lead to update conflicts. purchTable.update(); } Без версионности - после того как вы покупку подтвердили - в коробке фиксируются какие-то параметры (не только в самой строке покупки) и если вы хотите изменить уже подтвержденную покупку - то вам тоже надо сделать какой то "сброс" и затем подтвердить эту покупку повторно. В целом если использовать активацию обновления покупки как например сделано в purchCancel\updatePurchTable(), то дальше спокойно можно обновлять строки в цикле по update, без дополнительных reread
__________________
Sergey Nefedov |
|
|
|
|
|