|
18.01.2011, 13:54 | #1 |
Участник
|
У нас во всем приложении было всего 5 вызовов insert_recordset, причем только один из них реально использовался. Его мы заменили на обычный insert.
Хочу поделиться одним по-моему важным замечанием насчет размера кеша. В двухуровневой конфигурации и в трехуровневой с толстым клиентом размер кэша действительно составляет 25 по умолчанию. А вот в трехуровневой с тонким клиентом - 250! Так что при использовании триггера следует рассматривать "дыры" не меньше 250, а не 25, как заявлялось ранее. |
|
18.01.2011, 16:35 | #2 |
Moderator
|
Обнаружил тему SystemSequences - Выделение RecId, которую, наверное, неплохо иметь в виду при чтении текущей темы.
|
|
|
За это сообщение автора поблагодарили: Alenka (1). |
19.01.2011, 13:11 | #3 |
Moderator
|
Товарищ! Бойся инсерт_рекордсетов, в окрестности максимума RecId происходящих!
Axapta 3.0 SP4, СУБД - Oracle 10.
Исследовал поведение генератора RecId в зоне перехода от плюса к минусу, т.е. ...2147483646, 2147483647, -2147483648, -2147483647, -2147483646... Выяснил, что insert_recordset не особо церемонится с этой областью. При одномоментной вставке 694 записей со значения SystemSequences.nextVal = 2147483646 по окончании получил nextVal = -2147482956. Проверка: 2147483647 - 2147483646 + 1 + (-2147482956 - (-2147483648)) = 694. В таблице же, куда вставлялись записи, при этом обнаружились (средствами СУБД) монотонно возрастающие, все положительные (!), значения RecId от 2147483646 до 2147484339. Дальнейшие попытки открыть таблицу в Обозревателе Аксапты или перебрать ее строки при помощи while select успехом не увенчались. Инфолог сообщал об ошибке SQL "ORA-01455: переполнение при преобразовании столбца к целому типу данных". После выше упомянутой адаптации insert_recordset к построчной вставке всё гладенько перевалилось через ...2147483647, -2147483648... и далее побежало в минусе, закончившись на -2147482957. В Обозревателе теперь всё нормально открылось. |
|
|
За это сообщение автора поблагодарили: pitersky (2). |
24.01.2011, 18:47 | #4 |
Moderator
|
Триггер для дыр. Версия 2
Ну, еще раз и… если не красиво, то гораздо более уверенно.
Под влиянием части обсуждения в этой ветке, главным образом связанного с критичностью вопроса об insert_recordset, а также в стремлении доведения дела до конца, родилась вторая версия триггера. Теперь при попытке выполнения шага «новоеЗначение – староеЗначение» большего, нежели размер кэша (25 или 250) проверяется возможность его выполнения в области дыр и при невозможности размещения этого шага в пределах текущей дыры возникает ошибка времени выполнения. При этом параметры попытки сохраняются в специальном реестре отказов – таблице RecIdRefuseLog. Пользователь на клиенте получает сообщение об ошибке и откат транзакции. Далее предполагается, что пользователь сообщит о своей беде администратору, который заглянет в таблицу RecIdRefuseLog, увидит какой шаг пыталась сделать Аксапта, вычислит разность нового и старого значения, после чего «подкрутит» в таблице SystemSequences поле nextVal, принудительно установив его в начало следующей подходящей по размеру шага (разности) дыры, после чего пользователь попытается выполнить свою критичную операцию повторно. Это сценарий будет происходить только в том случае, если вы решили не избавляться от честных многострочных insert_recordset’ов путем их превращения в построчные одиночные insert’ы, как было описано выше. Если же вы произвели данную адаптацию (что настоятельно рекомендуется при работе с дырами – раз уж сам процесс в данном случае не совсем стандартен, то почему бы не сделать нестандартными и insert_recordset’ы ), то подобных ошибок не предвидится, и процесс генерации RecId будет гладко перетекать из одной дыры в другую. Однако, теперь вы знаете, что механизм защиты от больших шагов работает и гарантирует, что система не уйдет в тихое массовое повторное генерирование уже использованных идентификаторов RecId. О других ограничениях можно получить представление при ознакомлении с программным текстом триггера (на PL/SQL для Oracle): Код: ---------------------------------------------------------------- -- Триггер, управляющий генерацией RecId в дырах. Версия 2. ---------------------------------------------------------------- CREATE OR REPLACE TRIGGER SystemSequences_TBU BEFORE UPDATE ON SystemSequences REFERENCING NEW AS New OLD AS Old FOR EACH ROW WHEN ( SUBSTR(NLS_LOWER(Old.DataAreaId),1,3) = 'ppp' AND Old.Id = -1 AND Old.TabId = 0 ) DECLARE axCacheSize NUMBER(10); -- Axapta 3.0 Cache Size upAreaStart NUMBER(10); -- начало "верхней области" cntBetween NUMBER(10); cntAbove NUMBER(10); holesRange RecIdHoles%ROWTYPE; -- локальная процедура логирования отвергнутых попыток изменения nextVal PROCEDURE Stop_RecId_Generating (ReasonNote_in IN VARCHAR2) IS PRAGMA AUTONOMOUS_TRANSACTION; -- для того, чтобы можно было сделать локальный фискальный COMMIT при откате самого триггера BEGIN INSERT INTO RecIdRefuseLog (AttemtDate, OsUserName, OldNextVal, NewNextVal, ReasonNote) VALUES (SYSDATE, sys_context('UserEnv','OS_User'), :Old.NextVal, :New.NextVal, ReasonNote_in); COMMIT; RAISE_APPLICATION_ERROR(-20111,'RecId Generating Refuse: ' || ReasonNote_in); END; BEGIN -- админский ввод любого значения nextVal через "пароль" - специальное значение поля TabId (-111 выбрано произвольно) -- при этом все последующие проверки не работают -- это необходимо, например, когда при работающем уже триггере нужно установить начальное минимальное значение из таблицы дыр IF :New.TabId = -111 THEN :New.TabId := :Old.TabId; -- отмена изменения TabId, но nextVal при этом благополучно изменяется RETURN; END IF; upAreaStart := 1263764777; -- NextVal перед переключением - передвинуть вперед с запасом на 100 тысяч, чтобы не схватили, пока включаем триггер -- а также доиспользовали уже выделенные ранее RecId axCacheSize := 25; -- Axapta 3.0 Cache Size -- 25 или 250 - проверить при помощи: print new SystemSequence().getCacheSize(); pause; IF :Old.NextVal > 0 AND :New.NextVal < 0 THEN Stop_RecId_Generating('Причина 1 - алгоритм работает только на монотонно возрастающем участке') ; END IF; IF :Old.NextVal >= upAreaStart AND :New.NextVal > upAreaStart THEN -- если уже в верхней зоне, то никаких ограничений, -- это будет обычное успешное авершение ПОСЛЕ прохождения таблицы дыр RETURN; END IF; IF :Old.NextVal < upAreaStart AND :New.NextVal > upAreaStart THEN -- наверное, такого не будет в принципе из-за других ограничений Stop_RecId_Generating('Причина 2 - попытка перехода через начало верхней области'); END IF; -- проверяем находятся ли старое значение и новое значение + дальнейший резерв в одной дыре -- если да, то этим мы гарантируем, что следущему клиенту будет достаточно его 25 значений -- при использовании insert_recordset текущая разница:New.NextVal- :Old.NextVal может быть больше 25, поэтому мы делаем проверку (как бы еще раз после предыдущего выделения RecId) -- если разница точно равна 25, то эта проверка здесь была бы не нужна, так как мы обечпечиваем наличие 25 номеров на предыдущем шаге SELECT COUNT(*) INTO cntBetween FROM RecIdHoles h WHERE :Old.NextVal BETWEEN h.FromRecId AND h.ToRecId AND :New.NextVal + axCacheSize - 1 BETWEEN h.FromRecId AND h.ToRecId; IF cntBetween = 1 THEN -- мы полностью внутри одной из строк в таблице дыр (даже если :New.NextVal-:Old.NextVal > axCacheSize) -- это будет обычное успешное завершение ПРИ прохождении таблицы дыр RETURN; END IF; -- если дошли сюда, то cntBetween = 0, и наши значения не внутри одной дыры IF :New.NextVal-:Old.NextVal > axCacheSize THEN -- для insert_recordset здесь мы ничего поменять не сможем, поэтому СТОП Stop_RecId_Generating('Причина 3 - попытка сделать шаг больше размера кэша (подозревается insert_recordset)'); END IF; -- если дошли сюда, то однозначно будем устанавливать новое значение :New.NextVal -- количество диапазонов выше текущего значения :New.NextVal SELECT COUNT(*) INTO cntAbove FROM RecIdHoles h WHERE h.FromRecId > :New.NextVal AND h.ToRecId - h.FromRecId + 1 >= axCacheSize; -- на всякий случай, в принципе это условие гарантируется самой таблицей IF cntAbove = 0 THEN -- здесь мы уже не в таблице дыр (как минимум новым значением) IF :New.NextVal < upAreaStart THEN :New.NextVal := upAreaStart; END IF; ELSE -- иначе мы все еще в области, охватываемой таблицей дыр -- устанавливаем новое значение, равное FromRecId следующего (ближайшего сверху) диапазона SELECT * INTO holesRange FROM (SELECT * FROM RecIdHoles h WHERE h.FromRecId > :New.NextVal AND h.ToRecId - h.FromRecId + 1 >= axCacheSize -- на всякий случай, в принципе это условие гарантируется самой таблицей ORDER BY h.FromRecId) WHERE ROWNUM = 1; :New.NextVal := holesRange.FromRecId; END IF; EXCEPTION WHEN OTHERS THEN -- Consider logging the error and then re-raise RAISE; END SystemSequences_TBU; ---------------------------------------------------------------- -- Операторы создания таблицы дыр (выполнить последовательно) ---------------------------------------------------------------- CREATE TABLE RecIdHoles ( FROMRECID NUMBER(10) NOT NULL, TORECID NUMBER(10) NOT NULL ) CREATE UNIQUE INDEX RECIDHOLES_PK ON RECIDHOLES (FROMRECID, TORECID) ALTER TABLE RecIdHoles ADD ( CHECK (TORECID-FROMRECID+1>=25), -- здесь или 25, или 250, или иное желаемое обоснованное значение CONSTRAINT RECIDHOLES_PK PRIMARY KEY (FROMRECID, TORECID) ) ---------------------------------------------------------------- -- Таблица логирования отказов ---------------------------------------------------------------- CREATE TABLE RecIdRefuseLog ( ATTEMTDATE DATE, OSUSERNAME VARCHAR2(30 BYTE), OLDNEXTVAL NUMBER(10), NEWNEXTVAL NUMBER(10), REASONNOTE VARCHAR2(100 BYTE) )
Последний раз редактировалось Gustav; 24.01.2011 в 19:41. |
|
|
За это сообщение автора поблагодарили: sukhanchik (10). |