Выявил источник проблемы:
В DimensionStorage:SavePrivate()
X++:
new userConnection()
Притом данный коннект создается только в случае необходимости создания новой строки
DimensionAttributeValueCombination , при повторной необходимости использования аналитик берется уже ранее созданная, поэтому ошибочно тему закрыли.
При создании открывается новое соединение UserConnection, и почему то
соединение не снимается, а остается в пуле на некоторое время (более 10минут), настройки аосов выполнили согласно описанию выше постом.
При выполнении данного кода, часто вылетают ошибки ключа (один и тот же договор используется в двух разных журналах, одновременно выполняемых на разных потоках).
Пробовал добавлять в конце метода
UserConnection.Finalize(), однако результатов не принесло, соединения копятся гораздо быстрее, чем снимаются. Подсмотрено в методах NumberSeq, там аналогично используются userConnection, и проблем не возникает.
X++:
/// <summary>
/// Saves the current information and retrieves the record ID of the persisted combination.
/// </summary>
/// <returns>
/// The record ID of the combination.
/// </returns>
private recId savePrivate()
{
#OCCRetryCount
#LedgerSHA1Hash
DimensionSHA1Hash hash;
DimensionAttributeLevelValue dimAttrLevelValue;
DimensionAttributeValueGroup dimAttrValueGroup;
DimensionAttributeValueGroupCombination dimAttrValueGroupCombo;
DimensionAttributeValueCombination dimAttrValueCombo;
DimensionHierarchy accountStructure;
DimensionHierarchyLevel mainAccountSegment;
DimensionAttributeValue mainAccountDimAttrValue;
int hierarchyIndex;
int segmentIndex;
int previousSegmentIndex;
int i;
int hierarchyCount;
int segmentCountForHierarchy;
DimensionStorageSegment segment;
XDSServices xdsServices;
UserConnection userConnection;
LedgerDimensionBase savedComboId;
if (Debug::debugMode())
{
Debug::assert(this.hierarchyCount() > 0);
Debug::assert(segments != null);
Debug::assert(totalSegmentCount > 0);
DimensionStorage::validateCombinationIntegrity(this);
}
// Calculate the overall super-combo hash
hash = this.getComboHash();
if (hash == connull())
{
Debug::printDebug(strfmt('Warning: LedgerDimension of %1 is not created as no segments with DimensionAttributeValues exist (Is the backing entity instance missing?).', dimAttrValueCombo.DisplayValue));
// Don't save combinations without level values
initialComboId = 0;
initialHash = connull();
return 0;
}
else if (hash == initialHash)
{
// Nothing to do if nothing's changed
Debug::assert(DimensionAttributeValueCombination::exist(initialComboId));
return initialComboId;
}
// Turn off XDS to ensure the reference can be found.
xdsServices = new XDSServices();
xdsServices.setXDSState(0);
savedComboId = DimensionStorage::getSavedComboRecIdByHash(hash);
if (savedComboId)
{
initialComboId = savedComboId;
initialHash = hash;
return savedComboId;
}
// Create the main combination and group link in a separate transaction for smaller transaction scope to prevent blocking
userConnection = new userConnection();
userConnection.ttsBegin();
dimAttrValueCombo.setConnection(userConnection);
dimAttrValueGroupCombo.setConnection(userConnection);
try
{
// A matching was not found, so now insert a new combination. This will link to any existing sub-groups found or new sub-groups are inserted and linked
dimAttrValueCombo.DisplayValue = this.getComboDisplayValue();
dimAttrValueCombo.LedgerDimensionType = ledgerDimensionType;
dimAttrValueCombo.Hash = hash;
// Look up the account structure and main account if applicable
accountStructure = DimensionHierarchy::find(this.getHierarchyId(1));
//Debug::assert(accountStructure.RecId); // TODO: Enable once the unit test setup data correctly
if (accountStructure && (accountStructure.StructureType == DimensionHierarchyType::AccountStructure))
{
// Denormalize the account structure and main account
dimAttrValueCombo.AccountStructure = accountStructure.RecId;
// Look up the main account segment, which uses the hierarchy/dimension attribute
// index so this will be a cached lookup
mainAccountSegment = DimensionHierarchyLevel::findByDimensionHierarchyAndDimAttribute(
accountStructure.RecId,
DimensionAttribute::getMainAccountDimensionAttribute());
// If the hierarchy is an account structure, then the dimension hierarchy level
// will be indexed the same in the storage segment collection as in the account
// structure
if (mainAccountSegment && this.segmentCount() >= mainAccountSegment.Level)
{
// Look up the main account recid from the sepecified DAV. This will
// normally be a cached lookup since it was recently used to create the
// DAV that was passed to the storage object.
mainAccountDimAttrValue = DimensionAttributeValue::find(this.getSegment(mainAccountSegment.Level).parmDimensionAttributeValueId());
dimAttrValueCombo.MainAccount = mainAccountDimAttrValue.EntityInstance;
}
}
else if (accountStructure && (accountStructure.StructureType == DimensionHierarchyType::DefaultAccount))
{
// Denormalize the main account
Debug::assert(this.segmentCount() == 1);
// Look up the main account recid from the sepecified DAV. This will
// normally be a cached lookup since it was recently used to create the
// DAV that was passed to the storage object.
mainAccountDimAttrValue = DimensionAttributeValue::find(this.getSegment(1).parmDimensionAttributeValueId());
dimAttrValueCombo.MainAccount = mainAccountDimAttrValue.EntityInstance;
}
// Create the header value
dimAttrValueCombo.insert();
savedComboId = dimAttrValueCombo.RecId;
initialComboId = savedComboId;
initialHash = hash;
hierarchyCount = this.hierarchyCount();
for (hierarchyIndex = 1; hierarchyIndex <= hierarchyCount; hierarchyIndex++)
{
segmentCountForHierarchy = this.segmentCountForHierarchy(hierarchyIndex);
hash = this.getGroupHash(hierarchyIndex);
previousSegmentIndex = segmentIndex;
try
{
// Try to find an existing value group that matches what we need for the specified group - use the default connection (as it would have been saved by another process's connection anyway)
select firstOnly RecId from dimAttrValueGroup where dimAttrValueGroup.Hash == hash;
if (dimAttrValueGroup.RecId)
{
segmentIndex += segmentCountForHierarchy;
}
else
{
// Create the group and levels in the same separate transaction
dimAttrValueGroup.setConnection(userConnection);
dimAttrLevelValue.setConnection(userConnection);
dimAttrValueGroup.DimensionHierarchy = this.getHierarchyId(hierarchyIndex);
dimAttrValueGroup.Hash = hash;
dimAttrValueGroup.Levels = segmentCountForHierarchy;
dimAttrValueGroup.insert();
for (i = 0; i < segmentCountForHierarchy; i++)
{
segmentIndex++;
segment = this.getSegment(segmentIndex);
if (!segment.isEmpty())
{
dimAttrLevelValue.DimensionAttributeValueGroup = dimAttrValueGroup.RecId;
dimAttrLevelValue.DimensionAttributeValue = segment.parmDimensionAttributeValueId();
dimAttrLevelValue.DisplayValue = segment.parmDisplayValue();
dimAttrLevelValue.Ordinal = i + 1;
dimAttrLevelValue.insert();
}
}
}
dimAttrValueGroupCombo.DimensionAttributeValueGroup = dimAttrValueGroup.RecId;
dimAttrValueGroupCombo.DimensionAttributeValueCombination = savedComboId;
dimAttrValueGroupCombo.Ordinal = hierarchyIndex;
dimAttrValueGroupCombo.insert();
}
catch (Exception::DuplicateKeyException) // Finding or creating the DAVG
{
if (xSession::currentRetryCount() < #RetryNum)
{
segmentIndex = previousSegmentIndex; // Revert to previous position, since insert failed
retry;
}
Debug::assert(dimAttrValueGroupCombo.RecId != 0);
// Failed to insert a new group -- restart the transaction and try to find the full combination again (by outer catch)
userConnection.ttsAbort();
userConnection.ttsBegin();
throw Exception::DuplicateKeyException;
}
}
// Successfully inserted a completely new combination
userConnection.ttsCommit();
}
catch (Exception::DuplicateKeyException) // Finding or creating the DAVC
{
// Attempt to re-read the DAVC as another one was apparently inserted - use the default connection (as it would have been saved by another process's connection anyway)
savedComboId = DimensionStorage::getSavedComboRecIdByHash(hash);
if (savedComboId)
{
initialComboId = savedComboId;
initialHash = hash;
}
else if (xSession::currentRetryCount() < #RetryNum)
{
// The retry will be a rare case where we didn't find it, tried to insert, got dup, tried to reread and didn't find it again so will attempt to re-insert
retry;
}
else
{
// Record level security may have prevented the record from being found even with XDS off.
Debug::assert(savedComboId != 0);
}
// Failed to insert a new combination
userConnection.ttsAbort();
}
userConnection().finalize();//Добавлено для проверки
return savedComboId;
}
Напомню, текущая задача является импорт строк LedgerJournalTrans в количестве более 50млн, кол-во строк в CustTable составляет порядка 2х Млн. Соответственно при импорте каждой строки вызывается метод
X++:
DimensionStorage::getDynamicAccount(custAccount , LedgerJournalACType::Cust);
Временным решением нашли предварительное создание аналитик, а уже после загрузку данных:
X++:
static server void DimAttributeValueCombCREATE_AXFORUM(Args _args)
{
#define.maxRowCount(100000)
int64 tick1 = WinAPI::getTickCount64();
int countCurrent;
int countCreate;
int countMax = #maxRowCount;
int countCatch;
CustAccount custAccount;
CustTable custTable;
DimensionAttributeValueCombination dimComb;
return;//Снять в случае необходимости исполнения кода
while select AccountNum from custTable
order by custTable.AccountNum asc
notexists join RecId from dimComb
where custTable.AccountNum == dimComb.DisplayValue
{
countCurrent++;
if(countCurrent >= countMax)
break;
custAccount = custTable.AccountNum;
if(custAccount)
{
try
{
DimensionStorage::getDynamicAccount(custAccount , LedgerJournalACType::Cust);
countCreate++;
}
catch
{
countCatch++;
}
}
}
info(strFmt('countCurrent: (%1)', countCurrent));
info(strFmt('countCreate: (%1)', countCreate));
info(strFmt('countCatch: (%1)', countCatch));
info(strFmt("%1", time2str(ms2TimeOfDay(WinAPI::getTickCount64() - tick1),1,1)));
}