05.04.2011, 16:30 | #1 |
Участник
|
Модификация огромного количества (сотни тысяч) записей в Axapta 3.0 SP4
Давненько я с Аксаптой 3.0 не работал.
Мне необходимо модифицировать огромное количество записей в ряде таблиц в третьей Аксапте. В одной таблице может быть, скажем, 700 000 записей. Разумеется, одной транзакцией этого делать нельзя. Код у меня наподобие следующего: X++: Table1 table1; int bulk = 5000; // 5; int i; ; ttsbegin; while select forupdate table1 { i++; table1.Field1 = strfmt("Blah %1", i); table1.doupdate(); if (i mod bulk == 0) { ttscommit; ttsbegin; } } ttscommit; Что за дела? Я уже пытался использовать и while select и SelectForUpdate отдельно, и дополнительную переменную Table1 (одну для цикла, одну для find forupdate), и Query, и UserConnection, и вместо внешних ttsbegin/ttscommit использовать только внутренние, и чего я только не пытался - везде свои грабли. Чувствую, следующим шагом буду ориентироваться на диапазон RecId и просто делать кучу отдельных циклов по каждой таблице. У кого-то есть идеи что вообще происходит? На всякий случай - все это происходит в классе унаследованном от RunBaseBatch (но не в пакетной обработке). |
|
05.04.2011, 16:36 | #2 |
MCP
|
Можно сделать 1 основной цикл, в нем выбирать записи не для обновления. Потом внутри цикла открывать транзакцию, выбирать select forupdate из другой этой же таблицы но другой табличной переменной и обновлять? Т.е.:
X++: table1 table1, table1_Upd; ; while select table1 { ttsbegin; select forupdate * from table1_Upd where table1_Upd.RecId == table1.RecId; table1_Upd.Update(); ttscommit; } |
|
05.04.2011, 16:53 | #3 |
Участник
|
Кстати, да, если делать отдельный ttsbegin/ttscommit для каждой записи, все вроде работает. Я, наверное, на этом и остановлюсь.
X++: Table1 table1Loop; Table1 table1; int i; ; while select table1Loop { i++; ttsbegin; table1 = Table1::findRec(table1Loop.RecId, true); table1.Field1 = strfmt("Blah %1", i); table1.doupdate(); ttscommit; } Но мне почему-то казалось, что групповое обновление записей (скажем, 500 или 1000 в одной транзакции) на больших объемах даст ощутимый прирост производительности. Я не прав? И вот такой вот код уже не работает: X++: Table1 table1Loop; Table1 table1; int bulk = 5000; // 5; int i; ; ttsbegin; while select table1Loop { i++; table1 = Table1::findRec(table1Loop.RecId, true); table1.Field1 = strfmt("Blah %1", i); table1.doupdate(); if (i mod bulk == 0) { ttscommit; ttsbegin; } } |
|
05.04.2011, 17:23 | #4 |
северный Будда
|
а чем не устраивает update_recordset в одной транзакции?
__________________
С уважением, Вячеслав |
|
05.04.2011, 17:35 | #5 |
Участник
|
|
|
05.04.2011, 17:44 | #6 |
северный Будда
|
Цитата:
а поведение Аксапты ИМХО вполне логично. что такое закрытие транзакции как не обозначение границы операции с определённым набором данных? вполне естественно, что Аксапта, получив команду на открытие транзакции, заново формирует этот набор.
__________________
С уважением, Вячеслав |
|
05.04.2011, 18:33 | #7 |
Участник
|
Ради интереса, каким образом мой пример можно привести к update_recordset?
В Аксапте ttsbegin/ttscommit используется только для изменения/добавления/удаления данных. На простой while select без forupdate никакие ttsbegin/ttscommit влиять не должны. Посмотрите на мой последний прмер. В поздних версиях AX я таких проблем не встречал. Могу перепроверить, если интересно. Последний раз редактировалось Hyper; 05.04.2011 в 18:38. |
|
05.04.2011, 18:38 | #8 |
Axapta
|
Почему "разумеется"? Иногда можно, иногда даже нужно, иногда нельзя. Зависит от ситуации.
Цитата:
Сообщение от Hyper
да, если делать отдельный ttsbegin/ttscommit для каждой записи, все вроде работает. ...Но мне почему-то казалось, что групповое обновление записей (скажем, 500 или 1000 в одной транзакции) на больших объемах даст ощутимый прирост производительности...И вот такой вот код уже не работает:
X++: Table1 table1Loop; Table1 table1; int bulk = 5000; // 5; int i; ; while select table1Loop order by recId { if (i==0) ttsbegin; i++; table1 = Table1::findRec(table1Loop.RecId, true); table1.Field1 = strfmt("Blah %1", i); table1.doupdate(); if (i mod bulk == 0) { ttscommit; ttsbegin; } } if (i>0) ttscommit; |
|
05.04.2011, 18:46 | #9 |
Участник
|
Цитата:
Так тоже пробовал. Глючило - ругалось на что-то вроде unbalanced ttsbegin/ttscommit. |
|
05.04.2011, 18:50 | #10 |
Axapta
|
А почему нет?
Ну так надо сделать их balanced. Я свой код не проверял, Аксапты сейчас нет, но свиду вроде с транзакциями все нормально. |
|
05.04.2011, 19:16 | #11 |
Участник
|
Вроде бы чем больше измененных записей в одной транзакции, тем хуже Аксапта(/SQL?) справляется, причем при достижении определенного порога все еле ворочается?
Хотя верно, я, вроде, не совсем так пробовал. Сейчас проверю. |
|
05.04.2011, 19:53 | #12 |
Участник
|
Перепроверил - получилось, спасибо. Обидно, правда, что куча времени теряется на внутренний select:
X++: table1 = Table1::findRec(table1Loop.RecId, true); Подумалось, может быть сделать что-то вроде следующего: X++: Table1 table1; int bulk = 5000; // 5; RecId lastRecId; int i; ; do { i = 0; ttsbegin; while select forupdate table1 order by RecId where table1.RecId > lastRecId { table1.Field1 = strfmt("Blah %1", i); table1.doupdate(); lastRecId = table1.RecId; i++; if (i >= bulk) break; } ttscommit; } while (i >= bulk); Выглядит оптимальным решением, нет? Правда, работать будет быстро только при наличии уникально индекса по одному полю. Сейчас попробую. Последний раз редактировалось Hyper; 05.04.2011 в 20:05. |
|
05.04.2011, 20:24 | #13 |
Участник
|
Коллеги, я не программист, но разве так нельзя?
X++: while select table1 { i++; ttsbegin; table1.selectForUpdate(true); table1.reread(); table1.Field1 = strfmt("Blah %1", i); table1.doupdate(); ttscommit; }
__________________
Ivanhoe as is.. |
|
05.04.2011, 21:12 | #14 |
Участник
|
Можно, но reread = дополнительный select, что может оказаться накладным. К слову сказать если гарантировать отсутствие конфликтов (например запускать обработку на свободной базе), то можно попробовать и без него , но это от лукавого .
Обнаруженное поведение while select'а - это явный баг. Есть ещё вариант попробовать переписать цикл с использованием select ... next. В принципе то же самое, только вид сбоку, но кто его знает ... |
|
05.04.2011, 21:17 | #15 |
Участник
|
Я пытаюсь избавиться от двух зол: 1) отдельный ttsbegin/ttscommit для каждой записи (из 700 000 в одной таблице) 2) единственный ttsbegin/ttscommit для всей таблицы (700 000 записей) Оптимальным решением было бы разбить таблицу на, скажем, 70 кусков, т.е. изменить все записи за 70 транзакций. Для таблиц с уникальным индексом по единственному полю я, вроде, решение нашел, завтра продолжу тестирование. Для остальных, если не извращаться, надо будет выбрать (1) или (2). При гипотетическом отсутствии других вариантов какой из этих двух вариантов будет оптимальнее, кто как думает? Последний раз редактировалось Hyper; 05.04.2011 в 21:20. |
|
05.04.2011, 21:57 | #16 |
Moderator
|
Цитата:
Код: for(i=0;i<70;i++) { ttsbegin; while select forupdate table1 where (table1.recid mod 70)==i { .... table1.update(); } ttscommit; } |
|
05.04.2011, 22:21 | #17 |
Administrator
|
А обязательно исполнять запрос на X++? Можно ли пойти по "принципу 1С" - сформировать текст SQL запроса вида "UPDATE MyTable SET Field1=Value" и исполнить его в одной транзакции в отдельном подключении (new UserConnection()).
Не... я конечно понимаю, что правильнее писать на Х++, но если разница идет между тем - открывать курсор или не открывать, да еще и на большом объеме записей - то этот вариант тоже стоит рассмотреть. В общем-то в системе - тоже есть примеры использования прямого SQL. А в данном случае - создание отдельного подключения (connection) - вещь весьма оправданная - т.е. пока это подключение будет "висеть" - остальные честно смогут работать (если не будут конечно все массово пытаться также обновлять эту табличку). Причем, важно - что в этой конструкции у Вас не будет Where (или таблица дробится по компаниям?), а значит не нужно заморачиваться на наличие индекса по полю, фигурирующему в условии Where (ну а даже если и есть компании - то все равно есть индекс по dataareaid и recid).
__________________
Возможно сделать все. Вопрос времени |
|
05.04.2011, 22:24 | #18 |
Участник
|
Цитата:
В зависимости от версии SQL, от выставленного Recovery Model и от размера файла для логов. легко. для SQL - это не страшные объемы. Цитата:
Цитата:
возвращаемся к параметрам. 1. Если у вас SQL2000, то там есть блокировка на чтение. (или более старший SQL, но у вас установлен режим совместимости с SQL2000). только тогда имеет смысл заниматься "разбиением на, скажем, 70 кусков" Если же у вас более новый SQL, то заниматься ерундой не стоит. 2. Если у вас Recovery Model = Full, то на сколько бы кусков вы ни разбивали, все будет записываться в Transaction log. Если у вас Recovery Model = Simple, то вы выигрываете только на том, что каждая транзакция сразу очищается И файл транзакций НЕ растет. 3. вот и приходим к размеру лога. как вы изящно выразились "при достижении определенного порога все еле ворочается". просто транзакция заполняет весь лог, и СКЛ начинает увеличивать файл лога. на увеличение файла тратит значительное время. в SQL Management Studio вы можете посмотреть отчет (по-моему, Disk Usage) и ужаснуться сколько времени SQL тратит на увеличение размера. кроме того, исходя из своего опыта рискну предположить, что у вас таки SQL2005 или выше и параметры для файлов выставлены по-умолчанию. Дело в том, что начиная с SQL2005 файл Transaction Log по умолчанию растет по 1Мб (один мегабайт! - это ужасный параметр по умолчанию) =============== следовательно, перестаньте заниматься фигней. 700тыс записей - смешной объем как для Аксапты, так и для СКЛ. посмотрите в параметры СКЛ. прежде всего увеличьте минимальный размер Transaction Log до вменяемого значения (поставьте гиг 5) обязательно установите вменяемый размер прироста в фиксированных единицах (например, по 200-300Мб). Ни в коем случае не оставляйте по 1Мб. Так вы сократите фрагментацию как диска, так и внутреннюю SQL. Так вы минимизируете накладные расходы времени на рост Transaction Log. И не парьтесь "кусками". Сделайте или в одной нормальной транзакции. Или сделайте транзакцию на каждую запись. Разница только логическая - если прервете обработку, то либо отменятся все изменения, либо останется то, что сделано. |
|
|
За это сообщение автора поблагодарили: Hyper (1), sukhanchik (2), Poleax (1). |
06.04.2011, 01:58 | #19 |
Участник
|
Спасибо, но у клиента как раз SQL 2000. Надо мне было сразу уточнить. Смотрю пункт 1: Вот и занимаюсь. SQL 2000 каким-то образом влияет на следующие рекомендации, или они остаются в силе? Цитата:
А вот следующее было для меня откровением, я был уверен, что разница принципиальная, а не "только логическая": |
|
06.04.2011, 02:05 | #20 |
Участник
|
|
|