07.09.2013, 04:32 | #1 |
MCTS
|
Целостность данных при длительных запросах
Здравствуйте.
Есть запрос к InventSum, который делается через Query. Запрос представляет собой связку InventSum->InventTable->InventDim. Есть второй запрос к InventTrans, который делается через Query. Запрос представляет собой связку InventTrans->InventTable->InventDim. Есть третий запрос к InventSettlement, который делается через Query. Запрос представляет собой связку InventSettlement->InventTrans->InventTable->InventDim. Запросы используются для получения остатков на определенную дату, с заданными группировками. По сути, это переработка классов InventSumDate* с той лишь разницей, что позволяют получать остатки на дату не раздельно для каждой номенклатуры, а в цикле с любой группировкой. То есть вначале получаем текущие остатки из InventSum, затем вычитаем проводки InventTrans. Это позволяет выводить остатки на дату по большому списку номенклатур с максимальный скоростью, так как кол-во запросов в базу сводится к минимуму. Проблема возникает в следующем. В то время пока перебираются в цикле записи первого Query (по InventSum), кто-то создает проводку в InventTrans. Затем происходит выборка из InventTrans c помощью второго Query (уже с учетом этой созданной проводки). Таким образом, далее мы уже будем вычитать эти самые проводки из "старого" InventSum и получим неверные данные. Каким образом получить консистентные данные по InventSum, InventTrans и InventSettlement? ps: обрамление этих трех запросов с помощью ttsbegin;/ttscommt; не помогает. ps2: удалось снизить интервал времени между этими тремя запросами, переместив итерацию по queryRun-м в самый конец, уже после выполнения запроса в БД. ps3: возможно, поможет обрамление (дополнительно к аксаптовым ttsbegin;ttscommit) userConnection.ttsbegin()/userConnection.ttscommit(). Но, вроде как, для курсоров нужно делать setConnection(), а здесь это не применимо, так как работа идет с Query. |
|
07.09.2013, 11:58 | #2 |
NavAx
|
Ничего не поможет.
Мы эту проблему решали так: Делали два запроса по InventSum в начале и в конце расчетов, по позициям, которые изменились пересчитывали еще раз, и так до тех пор, пока все не совпадало. Иногда это может приводить к довольно долгому расчету, если паралельно разносятся несколько больших журналов. |
|
|
За это сообщение автора поблагодарили: Logger (3), Eldar9x (4). |
07.09.2013, 12:10 | #3 |
Участник
|
|
|
07.09.2013, 12:41 | #4 |
MCTS
|
Цитата:
Ничего не поможет.
Мы эту проблему решали так: Делали два запроса по InventSum в начале и в конце расчетов, по позициям, которые изменились пересчитывали еще раз, и так до тех пор, пока все не совпадало. Иногда это может приводить к довольно долгому расчету, если параллельно разносятся несколько больших журналов. Цитата:
версия то какая? и что у вас с оптимистическими блокировками?
|
|
07.09.2013, 12:50 | #5 |
Модератор
|
А при чем тут это если отчет несколькими запросами рассчитывается и сериализуемых блокировок на INVENTTRANS / INVENTSUM в процессе рассчета (я надеюсь) не накладывается ?
Eldar9x, вариантов два: - примите как факт что некоторые (как правило незначительные) расхождения при рассчете остатков по складу "задним числом" (иначе обошлись бы INVENTSUM и INVENTTRANS не шерстили бы) на рабочей БД теоретически могут быть by design - стройте рядом с рабочей (OLTP) базой данных AX свой DWH (с блэкджеком и шлюхами) и выгружайте данные в него (что в принципе тоже не так просто как может показаться, особенно при работе 24x7)
__________________
-ТСЯ или -ТЬСЯ ? |
|
07.09.2013, 13:09 | #6 |
MCTS
|
Цитата:
сериализуемых блокировок на INVENTTRANS / INVENTSUM в процессе рассчета (я надеюсь) не накладывается ?
Цитата:
- примите как факт что некоторые (как правило незначительные) расхождения при рассчете остатков по складу "задним числом" (иначе обошлись бы INVENTSUM и INVENTTRANS не шерстили бы) на рабочей БД могут быть by design
- стройте рядом с рабочей (OLTP) базой данных AX свой DWH (с блэкджеком и шлюхами) и выгружайте данные в него (что в принципе тоже не так просто как может показаться, особенно при работе 24x7) |
|
07.09.2013, 13:17 | #7 |
MCTS
|
Имеет ли смысл в данной ситуации делать следующее:
X++: UserConnection userConnection = new UserConnection(); ttsbegin; userConnection.ttsbegin(); selectRemains(); // выбираем остатки с помощью Query из InventSum и InventTrans userConnection.ttcommit(); ttscommit; |
|
07.09.2013, 18:35 | #8 |
Участник
|
нет, смысла не имеет userConnection, если к нему не привязывать запросы или курсор, ни на что не влияет. АОС и так держит пул этих соединений с БД, и одно из них вы используете в выборке. "Вложенное" соединение - это просто ещё одно соединение из пула, поддерживаемого АОСом, как если бы ещё одна клиентская сессия запустилась бы и что-то читала из БД.
|
|
|
За это сообщение автора поблагодарили: Eldar9x (4). |
07.09.2013, 19:30 | #9 |
MCTS
|
Ясно, спасибо. А каким образом привязать userConnection к запросу (к Query) ?
|
|
08.09.2013, 00:47 | #10 |
Участник
|
Честно говоря, не знаю, ни разу не видел примера использования отдельного соединения с Query/QueryRun - только с табличными курсорами и обычными select'ами либо с прямыми SQL-запросами.
|
|
09.09.2013, 08:45 | #11 |
NavAx
|
|
|
09.09.2013, 10:44 | #12 |
MCTS
|
На одном из проектов для ускорения подсчета остатков на дату на паллетах делали табличку, где хранились изменения остатков на дату: код паллеты, остаток, дата начала, дата окончания. Места она занимает немного, вставляются записи быстро и остаток на дату можно получить элементарным запросом с фильтром по дате. Можно то же самое сделать и для общего случая.
__________________
I could tell you, but then I would have to bill you. Последний раз редактировалось twilight; 09.09.2013 в 10:46. |
|
09.09.2013, 16:23 | #13 |
Участник
|
Если версия 4 и выше, то можно использовать в первом запросе таблицу inventSumDelta.
Пример использования смотри в классе InventUpd_Reservation метод updateReserveMore |
|
09.09.2013, 17:12 | #14 |
Moderator
|
Я этого никогда не пробовал, но можно попытаться. Начиная с версии SQL 2005 есть такой специальный уровень изоляции транзакций: SNAPSHOT ISOLATION. Если ты в скрипте на T-SQL пишешь SET TRANSACTION ISOLATION SNAPSHOT, а потом начинаешь транзакцию через вызов BEGIN TRANSACTION, то в системе включается версионная машина и твои запросы не видят ВСЕ изменения данных, которые были сделаны после того как ты выполнил вызов BEGIN TRANSACTION. То есть - консистентное состояние сохраняется не в рамках одного запроса, а для всех запросов внутри транзакции.
Есть, правда, некоторые "НО":
В общем - я бы попробовал все-таки тупо засовывать уровень изоляции через connection/statement, а потом вызвал бы connection.ttsbegin(), чтобы начать транзакцию. Есть большая надежда (хотя я и не уверен), что менежеру соединений в AOS хватит ума выделить отдельное соединение в тот момент когда ты connection вызываешь, а не в тот момент когда ты транзакцию начинаешь. Кроме того, помнится мне говорили, что то самое разделяемое соединение используется ТОЛЬКО логикой на формах, а если ты пишешь запрос в классе, то система использует выделенное соединение, которое прикрепляется к пользовательской сесии до тех пор, пока класс не отработает... Последний раз редактировалось fed; 09.09.2013 в 17:23. |
|
09.09.2013, 22:59 | #15 |
Участник
|
|
|
10.09.2013, 06:33 | #16 |
Moderator
|
Не совсем. Там требуют включать Read Committed Snapshot Isolation. В этом режиме, в режиме Read Committed включается версионная машина (ВНУТРИ ОДНОГО ЗАПРОСА) не ставяться блокировки чтения. SNAPSHOT ISOLATION - это отдельный режим изоляции транзакций и для его поддержки надо другую опцию у базы данных включать.
|
|
|
За это сообщение автора поблагодарили: belugin (5). |
10.09.2013, 12:45 | #17 |
NavAx
|
Цитата:
X++: static void edd_testUserConnection(Args _args) { UserConnection userConnection; InventTable inventTable; InventSum inventSum; Query query; QueryBuildDataSource qbds; QueryBuildRange qdr; QueryRun queryRun; boolean useAlterConnection = true; int i; ; query = new Query(); qbds = query.addDataSource(tableNum(InventSum)); qdr = qbds.addRange(fieldNum(InventSum, ItemId)); qbds.addSelectionField(fieldNum(InventSum, PhysicalInvent), SelectionField::Sum); qbds.addSortField(fieldNum(InventSum, ItemId)); qbds.orderMode(OrderMode::GroupBy); if (useAlterConnection) { userConnection = new UserConnection(); inventSum.setConnection(userConnection); } while select inventTable { i++; qdr.value(queryValue(inventTable.ItemId)); queryRun = new QueryRun(query); queryRun.setCursor(inventSum); queryRun.next(); inventSum = queryRun.getNo(1); info(strfmt("%1: %2", inventTable.ItemId, inventSum.PhysicalInvent)); sleep(1000); if (i >= 20) { break; } } } |
|
11.09.2013, 19:13 | #18 |
Участник
|
Как вариант
1. В таблице InventTrans установить свойства CreatedDate = Yes CreatedTime = Yes 2. Непосредственно перед выборкой в InventSum сохранить в переменные systemDateGet() и TimeNow() 3. В запрос по InventTrans непосредственно перед выборкой добавить примерно такое условие (пишу в псевдокоде для Query надо будет адаптировать) X++: where
(...)
AND (InventTrans.createdDate < systemDateGet()
OR (InventTrans.createdDate = systemDateGet() AND InventTrans.createdTime <= timeNow())) Вопрос только в том, какое именно время записывается в CreatedTime. В смысле, это время SQL-сервера, AOS или клиента? Поскольку, если это все 3 физически разных компьютера, то их время может различаться. Если не ошибаюсь, то это должно быть время AOS. Т.е. timeNow() надо вычислять на стороне сервера
__________________
- Может, я как-то неправильно живу?! - Отчего же? Правильно. Только зря... |
|
|
За это сообщение автора поблагодарили: Eldar9x (5). |
11.09.2013, 21:11 | #19 |
Участник
|
тогда уж ещё и ModifiedDate и ModifiedTime. Во время формирования первого запроса могут быть разнесены ранее созданные поводки. И что делать с этими изменёнными проводками? заново пересчитывать данные по таким номенклатурам?
|
|
11.09.2013, 21:27 | #20 |
Участник
|
Да. Наверное лучше использовать Modified вместо Create. Разумеется, если в процессе выборки не выполняется закрытие склада
__________________
- Может, я как-то неправильно живу?! - Отчего же? Правильно. Только зря... |
|