27.08.2022, 12:53 | #1 |
Участник
|
D365, SysDatabaseLog, поле NewData
В ах2009 и ах2012 детали каждой записи лога хранятся в контейнере в поле SysDatabaseLog.Data.
Например, мы настроили логирование изменений в таблице CustTable, обновили в записи таблицы CustTable какое-то поле. Тогда в SysDatabaseLog появится запись, у которой в поле Data будут храниться значения измененных полей. Помимо изменённого нами поля там будут ещё поля типа modifiedDateTime, modifiedBy, recVersion. В D365 в SysDatabaseLog для хранения деталей лога используется новое поле NewData. Это строковое поле типа Memo. Заполняется оно с помощью триггеров на уровне БД. У этих триггеров вскрылись следующие неприятные моменты: 1. Если мы хотим логировать update только отдельных полей в CustTable и думаем что в SysDatabaseLog будут появляться записи только при изменении этих полей, то мы ошибаемся. Записи будут появляться при изменении любого поля. 2. В поле NewData сохраняется значение всех полей, на которые мы настроили ведение логов. Даже если эти поля не изменялись - всё равно туда записывается значение этих полей. Два раза, типа старое и новое. Что ожидаемо ведёт к чрезмерному росту таблицы SysDatabaseLog . 3. Значения полей типа utcdatetime сохраняются в newdata без секунд. Может конечно у нас каких-то настроек не хватает, чтоб секунды сохранялись. Вот такая подлянка от микрософта.
__________________
Дмитрий |
|
|
За это сообщение автора поблагодарили: trud (2), sukhanchik (4). |
27.08.2022, 15:19 | #2 |
Участник
|
Цитата:
Есть какие-то случаи, когда запись в журнале базы данных создается даже тогда изменились поля, которые не настроены для отслеживания. До Ax3.0 включительно настройка по полям работала. Начиная с обновления Ax3.0, в котором появилось поле RecVersion этот механизм перестал учитывать настройки полей. Возможно это из-за RecVersion, возможно что-то там переделали, но факт на лицо. Причем, игнорирование настроек изменений по полям отслеживается не на всех таблицах (какая закономерность тут работает так и не понял). Мы, в свое время, наткнулись на это еще в DAX4 при настройке журнала базы данных по InventTableModule. |
|
|
За это сообщение автора поблагодарили: sukhanchik (4). |
27.08.2022, 19:25 | #3 |
Участник
|
Если посмотреть на код триггера
X++: AND ( UPDATE(KnownAs) OR UPDATE(Name) OR UPDATE(NV_ParentCompany) ) X++: IF (@spLogType = 3 AND ( UPDATE(KnownAs) OR UPDATE(Name) OR UPDATE(NV_ParentCompany) ) ) --Update BEGIN INSERT INTO SYSDATABASELOG (NEWDATA, LOGRECID, LOGTYPE, TABLE_, DESCRIPTION, USERNAME, CREATEDBY, CREATEDTRANSACTIONID, DATAAREAID, PARTITION, SEQUENCENUMBER) SELECT '03::' + + 'KnownAs' + ', ÿþ ' + I.KNOWNAS + ' ÿþ ' + D.KNOWNAS + ' ÿþ ' + 'Name' + ', ÿþ ' + I.NAME + ' ÿþ ' + D.NAME + ' ÿþ ' + 'NV_ParentCompany' + ', ÿþ ' + CONVERT(nvarchar(max), I.NV_PARENTCOMPANY) + ' ÿþ ' + CONVERT(nvarchar(max), D.NV_PARENTCOMPANY) + ' ÿþ ' + 'CreatedBy' + ', ÿþ ' + I.CREATEDBY + ' ÿþ ' + D.CREATEDBY + ' ÿþ ' + 'CreatedDateTime' + ', ÿþ ' + CONVERT(nvarchar(max), I.CREATEDDATETIME) + ' ÿþ ' + CONVERT(nvarchar(max), D.CREATEDDATETIME) + ' ÿþ ' + 'ModifiedBy' + ', ÿþ ' + I.MODIFIEDBY + ' ÿþ ' + D.MODIFIEDBY + ' ÿþ ' + 'ModifiedDateTime' + ', ÿþ ' + CONVERT(nvarchar(max), I.MODIFIEDDATETIME) + ' ÿþ ' + CONVERT(nvarchar(max), D.MODIFIEDDATETIME) + ' ÿþ ' + 'Partition' + ', ÿþ ' + CONVERT(nvarchar(max), I.PARTITION) + ' ÿþ ' + CONVERT(nvarchar(max), D.PARTITION) + ' ÿþ ' + 'RecId' + ', ÿþ ' + CONVERT(nvarchar(max), I.RECID) + ' ÿþ ' + CONVERT(nvarchar(max), D.RECID) + ' ÿþ ' + 'RecVersion' + ', ÿþ ' + CONVERT(nvarchar(max), I.RECVERSION) + ' ÿþ ' + CONVERT(nvarchar(max), D.RECVERSION) + ' ÿþ ' , I.RECID, 2, 1268,I.PARTYNUMBER + ',' + I.NAME,@userId, @userId, 0, 'dat', I.PARTITION, @seqNo FROM INSERTED I LEFT OUTER JOIN DELETED D ON I.RECID = D.RECID; END; Я в свое время словил в этом триггере ошибку, когда строка логирования обрезалась 1000-ю символами, предложил вариант с добавлением CONVERT(nvarchar(max) ..) - пофиксили оперативно. |
|
|
За это сообщение автора поблагодарили: sukhanchik (8). |
03.09.2022, 09:52 | #4 |
Участник
|
Цитата:
Более детальный анализ показал что ошибка из пункта 1 возникает только в тех таблицах, которые участвуют в наследовании (касается это и "родителей" и "детей"). Конкретно я столкнулся с такой проблемой в таблице DirPerson, которая отнаследована от DirPartyTable. У таблицы DirPerson (фактически в БД у таблицы DIRPARTYTABLE) код в триггере выглядит так : X++: ... IF (@spLogType = 3 ) --Update BEGIN INSERT INTO SYSDATABASELOG (NEWDATA, LOGRECID, LOGTYPE, TABLE_, DESCRIPTION, USERNAME, CREATEDBY, CREATEDTRANSACTIONID, DATAAREAID, PARTITION, SEQUENCENUMBER) SELECT '03::' + + 'BirthDay' + ', ÿþ ' + CONVERT(nvarchar(max), I.BIRTHDAY) + ' ÿþ ' + CONVERT(nvarchar(max), D.BIRTHDAY) + ' ÿþ ' ... X++: ... IF (@spLogType = 3 AND ( UPDATE(ConnectorName) OR UPDATE(ConnectorProperties) OR UPDATE(IsTest) ) ) --Update BEGIN INSERT INTO SYSDATABASELOG (NEWDATA, LOGRECID, LOGTYPE, TABLE_, DESCRIPTION, USERNAME, CREATEDBY, CREATEDTRANSACTIONID, DATAAREAID, PARTITION, SEQUENCENUMBER) SELECT '03::' + + 'ConnectorName' + ', ÿþ ' + I.CONNECTORNAME + ' ÿþ ' + D.CONNECTORNAME + ' ÿþ ' ...
__________________
Дмитрий |
|
|
За это сообщение автора поблагодарили: Raven Melancholic (5), S.Kuskov (5). |
03.09.2022, 15:26 | #5 |
Участник
|
И опять иерархия наследования таблиц, грустно.
Я считал, что те проблемы, которые наследование DAX2012 создало в ранее существующих механизмах, это просто проблемы роста. Но вот уже D365 идет по планете, а воз и ныне там. Впечатление, что команда предложившая и реализовавшая наследование таблиц, появилась на проекте, что-то сделала и слиняла в другие проекты. |
|
04.09.2022, 12:11 | #6 |
Участник
|
Цитата:
Для преобразования полей типа datetime в строку в триггере используется функция CONVERT(nvarchar(max), ...) https://docs.microsoft.com/ru-Ru/sql...l-server-ver15 А в MS SQL Server формат преобразования datetime по умолчанию - mon dd yyyy hh:miAM (or PM) . Если мы хотим секунды - необходимо в функцию CONVERT добавлять параметр style. Например, style 120 - это "yyyy-mm-dd hh:mi:ss (24h)".
__________________
Дмитрий |
|
04.09.2022, 12:37 | #7 |
Administrator
|
Цитата:
Сообщение от Raven Melancholic
И опять иерархия наследования таблиц, грустно.
Я считал, что те проблемы, которые наследование DAX2012 создало в ранее существующих механизмах, это просто проблемы роста. Но вот уже D365 идет по планете, а воз и ныне там. Впечатление, что команда предложившая и реализовавшая наследование таблиц, появилась на проекте, что-то сделала и слиняла в другие проекты. 1. Реализация наследования в AX2012RTM. Когда реально каждая таблица, участвующая в наследовании была реальной таблицей в БД. 2. Реализация наследования в AX2012R2. Когда всю иерархию таблиц слили по структуре данных в одну корневую родительскую таблицу. Т.е. теперь физически с т.з. БД любой запрос к подчиненной таблице в иерархии (в АХ) превращается (в БД) в запрос к корневой родительской таблице с фильтром по полю InstanceRelationType, в котором сидит TableId подчиненной таблицы. При этом в такой реализации использовать наследование таблиц абсолютно бессмысленно - проще сделать одну большую таблицу и уже с ней работать. Как минимум с индексами проблем не будет (в АХ нельзя сделать индекс с участием поля из подчиненной таблицы и родительской таблицы одновременно. А это напрямую влияет на производительность в том случае, когда этот индекс сильно нужен) Ну т.е. (как вариант развития событий) сначала сделали "по уму", затем видимо "не взлетело" по производительности - сделали костыль, "проштрафившуюся" команду уволили и продолжили с этим жить.
__________________
Возможно сделать все. Вопрос времени |
|
|
За это сообщение автора поблагодарили: twilight (1). |
04.09.2022, 13:02 | #8 |
MCTS
|
Так в первом варианте реализации даже теоретически нельзя индекс такой сделать. Т. е. получается концепция наследования таблиц в целом нежизнеспособна?
__________________
I could tell you, but then I would have to bill you. |
|
04.09.2022, 13:26 | #9 |
Участник
|
Цитата:
Концепция из пункта 1 могла бы нормально жить. Когда в БД каждая таблица - отдельно. Непонятно почему этот вариант отвергли.
__________________
Дмитрий |
|
05.09.2022, 07:30 | #10 |
Administrator
|
Цитата:
Цитата:
Собственно - изменение архитектуры БД, частичная нормализация таблиц и привели к тому, что AX 2012 стала требовать (на демо-виртуалке) минимум 32 Гб, в то время, как для AX 2009 4 Гб хватало за глаза. В D365FO в демо-виртуалке такого "взрывного роста" к требованиям уже не случилось, но надо понимать, что: 1. Структуру БД радикально уже не меняли 2. Убрали клиента, который использовал определенные ресурсы системы. 3. Скорость работы в системе при этом не увеличилась
__________________
Возможно сделать все. Вопрос времени |
|
05.09.2022, 14:46 | #11 |
Участник
|
Цитата:
Выяснилось что строковые поля обрезаются до 1000 символов. Пришлось писать SQL-запросы с помощью классов .NET.
__________________
Дмитрий |
|
05.09.2022, 16:06 | #12 |
Участник
|
А в связи с тем, что заполнение переехало на уровень триггеров указание common.skipDataBaseLog(true) теперь работает?
|
|
05.09.2022, 16:43 | #13 |
Участник
|
Цитата:
Но в триггере есть такой участок кода : X++: SELECT @userId = DBO.SysGetUserIdFromContextInfo(); SELECT @sessionId = DBO.SysGetSessionIdFromContextInfo(); IF EXISTS(SELECT TOP 1 TABLEID FROM SYSSKIPDATABASELOG WHERE USERID = @userId AND SESSIONID = @sessionId AND TABLEID = 13271) BEGIN RETURN; END
__________________
Дмитрий |
|
11.09.2022, 12:59 | #14 |
Участник
|
Обнаружилось ещё одно "новшество" в форме ЖБД в D365.
На вкладке "История" теперь нельзя увидеть изменения регистра в строковых полях. Имеются в виду случаи когда вы в поле поменяли значение с "test" на "Test". В форме при отображении эти изменения игнорируются. А в журнал базы данных они попадают. X++: if (fieldId != 0 && (!System.String::IsNullOrEmpty(newValue) || !System.String::IsNullOrEmpty(oldValue))) { // If field was saved as array field, we use the field index to get the field id fieldId = this.getExtendedFieldIdByIdx(useExtendedFieldId, fieldId, SysDatabaseLogDataParser::IsArrayFieldName(fieldNameDb) ? idx : 1); if (newValue != oldValue && (this.LogType != DatabaseLogType::Update || fieldName != 'RecVersion')) { list.addStart([fieldId, newValue, oldValue]); } } Если в этом участке кода переменные newValue и oldValue сравнивать с помощью newValue.Equals(oldValue), то всё нормализуется и мы в форме ЖБД увидим изменения, связанные с регистром текста.
__________________
Дмитрий |
|
|
За это сообщение автора поблагодарили: S.Kuskov (2). |
|
|