Как решить проблему плохой инициализации коллекции nHibernate - PullRequest
2 голосов
/ 16 апреля 2011

nHibernate3;извлечение записей 4xxx из схемы данных EAV.Когда nHibernate, или .NET, впервые инициализирует эти коллекции, мы видим серьезное наказание.Последующие звонки, кажется, работают более эффективно.Выполнение тех же запросов в SQL Server Management Studio приводит к ожидаемому быстрому возврату.

Использование Fluent и отображение времени выполнения вместо .hbm.xml;Любопытно, поможет ли здесь сериализованное сопоставление?

nHibernate Profiler и log4net, похоже, не давали мне много сил.В этом процессе гидратируется в общей сложности около 140 000 объектов.

Прикреплен снимок экрана с моей трассировкой производительности dotTrace, показывающий штраф за инициализацию коллекции: dotTrace of slow nHibernate collection initialization

Попробовал объединение и нетерпеливые типы выборки,без видимых результатов, но я не уверен на 100%, что я правильно их реализовал - нужно ли указывать только родительский элемент или же нужно пометить дочерние таблицы?

var products = ((HandleSession)_handleSession).Session.CreateCriteria(typeof(Product))
                    .SetFetchMode("Product", FetchMode.Eager)
                    .List<Product>()
                    .AsEnumerable();

С оптимизатором отражениявключен (я думаю) через web.config: With reflection optimizer enabled

Это то место, где больше всего времени тратится:

return new ProductList(products.Select(p => p.ToProductContract()));

Это просто метод расширения, который делает это:

public static ProductContract ToProductContract(this Product product)
        {
            return new ProductContract
                       {
                           Name = product.ProductName,
                           ProductTypeName = product.ProductType.ProductTypeName,
                           UpdateTimeStamp = product.UpdateDateTime,
                           ProductNumber = product.ProductNumber,
                           Attributes = product.ProductAttributes.ToCommonAttribute().ToList(),
                           GroupCategories = product.ProductGroups.ToGroupCategory().ToList(),
                           PublicUniqueId = product.PublicUniqueId
                       };
        }

mappings:

internal class ProductMapping : ClassMap<Product>
    {
        private const string _iscurrentindicator = "IsCurrentIndicator=1";

        public ProductMapping()
        {
            Table("Product");
            Id(Reveal.Member<Product>("ProductId")).GeneratedBy.Identity().Column("ProductID");
            Map(x => x.ProductNumber).Column("ProductNumber").Not.Nullable();
            Map(x => x.ProductName).Column("ProductName").Not.Nullable();
            Map(x => x.InsertDateTime).Column("InsertedDateTime").Nullable().ReadOnly();
            Map(x => x.UpdateDateTime).Column("UpdatedDateTime").Nullable();
            Map(x => x.PublicUniqueId).Column("ProductGUID").Generated.Insert();

            References(x => x.ProductType).Column("ProductTypeId").Not.Nullable();
            HasMany(x => x.ProductAttributes)
                .KeyColumn("ProductId")
                .Inverse()
                .Fetch
                .Subselect()
                .Where(_iscurrentindicator)
                .Cascade
                .SaveUpdate();

            HasMany(x => x.ProductGroups).KeyColumn("ProductId").Fetch.Subselect().Where(_iscurrentindicator);
            DynamicUpdate();
            DynamicInsert();
            BatchSize(500);
        }
    }

internal class ProductGroupMapping : ClassMap<ProductGroup>
    {
        public ProductGroupMapping()
        {
            Table("ProductGroup");
            Id(x => x.ProductGroupId).Column("ProductGroupId").GeneratedBy.Identity();
            References(x => x.Product).Column("ProductId").Not.Nullable();
            References(x => x.Group).Column("GroupId").Not.Nullable();
            //Where("IsCurrentIndicator=1");
        }
    }

internal class ProductAttributeMapping : ClassMap<ProductAttribute>
    {
        public ProductAttributeMapping()
        {
            Table("ProductAttribute");
            LazyLoad();
            Id(x => x.ProductAttributeId).GeneratedBy.Identity().Column("ProductAttributeID");
            References(x => x.Product).Column("ProductID").Not.Nullable();
            References(x => x.Attribute).Column("AttributeID").Not.Nullable().Fetch.Join();
            Map(x => x.PositionNumber).Column("PositionNumber").Nullable();
            Map(x => x.ValueText).Column("ValueText").Nullable();
            Map(x => x.ValueBinary).Column("ValueBinary").Nullable();

            Component(x => x.OperationalAuditHistory, m =>
                        {
                            Table("ProductAttribute");
                            m.Map(x => x.ExpirationDateTime).Column("ExpirationDateTime").Nullable();
                            m.Map(x => x.IsCurrent).Column("IsCurrentIndicator").Not.Nullable();
                            m.Map(x => x.OperationCode).Column("OperationCode").Nullable();
                            m.Map(x => x.OperationDateTime).Column("OperationDateTime").Nullable();
                            m.Map(x => x.OperationSystemName).Column("OperationSystemName").Nullable();
                            m.Map(x => x.OperationUserName).Column("OperationUserName").Nullable();
                            m.Map(x => x.LastUserPriority).Column("LastUserPriority").Nullable();
                        });

            DynamicInsert();
            BatchSize(50);
        }
    }

К сожалению, с Future у меня все еще есть похожие результаты.Вот новый след;На данный момент я переключился на Release и x64 для ключевых проектов, так что времена ниже, но пропорции все еще почти одинаковы;а также с .Eager:

var products = ((HandleSession) _handleSession).Session.CreateCriteria(typeof (Product))
                    .SetFetchMode("ProductAttribute", FetchMode.Join)
                    .SetFetchMode("ProductGroup", FetchMode.Join)
                    .SetFetchMode("ProductType", FetchMode.Join)
                    .Future<Product>()
                    .AsEnumerable();

dotTrace - Release mode, targeting x64, with .Future()

Сгенерированный SQL с .Eager и. Будущее на месте:

ВЫБРАТЬ this_.ProductID какProductID0_1_, this_.ProductNumber, как ProductN2_0_1_, this_.ProductName как ProductN3_0_1_, this_.InsertedDateTime как Inserted4_0_1_, this_.UpdatedDateTime как UpdatedD5_0_1_, this_.ProductGUID, как ProductG6_0_1_, this_.ProductTypeId, как ProductT7_0_1_, producttyp2_.ProductTypeID как ProductT1_6_0_, producttyp2_.ProductTypeName, как ProductT2_6_0_ ОТПродукт this_ внутреннее объединениеValueText2_1_, productatt0_.ValueBinary как ValueBin4_2_1_, productatt0_.ProductID в качестве ProductID2_1_, productatt0_.AttributeID в качестве Attribut6_2_1_, productatt0_.ExpirationDateTime, как Expirati7_2_1_, productatt0_.IsCurrentIndicator как IsCurren8_2_1_, productatt0_.OperationCode как Operatio9_2_1_, productatt0_.OperationDateTime как Operati10_2_1_, productatt0_.OperationSystemName как Operati11_2_1_, productatt0_.OperationUserName как Operati12_2_1_, productatt0_.LastUserPriority как LastUse13_2_1_, attribute1_.AttributeId, как Attribut1_1_0_, как attribute1_.AttributeNameAttribut2_1_0_, attribute1_.DisplayName, как DisplayN3_1_0_, attribute1_.DataTypeName как DataType4_1_0_, attribute1_.ConstraintText как Constrai5_1_0_, attribute1_.ConstraintMin как Constrai6_1_0_, attribute1_.ConstraintMax как Constrai7_1_0_, attribute1_.ValuesMin как ValuesMin1_0_, attribute1_.ValuesMax как ValuesMax1_0_, attribute1_.Precision как Precision1_0_ ОТProductAttribute productatt0_ внутреннее соединение Атрибут attribute1_ для productatt0_.AttributeID = attribute1_.AttributeId WHERE (productatt0_.IsCurrentIndicator = 1) и productatt0_.ProductId в (выберите this_.ProductID ОТ продукта this_ внутренний join ProductType producttyp2_ для this_.ProductTypeId = producttyp2_.ProductTypeID)

ВЫБРАТЬ productgro0_.ProductId в качестве ProductId1_, productgro0_.ProductGroupId в качестве ProductG1_1_, productgro0_.ProductGroupI0G0 как я.productgro0_ ГДЕ (Productgro0_.IsCurrentIndicator = 1) и productgro0_.ProductId in (выберите this_.ProductID ИЗ ПРОДУКТА this_ внутреннее соединение ProductType producttyp2_ on this_.ProductTypeId = producttyp2_.ProductTypeID)

Ответы [ 3 ]

8 голосов
/ 16 апреля 2011

1) Сериализованное отображение только поможет сократить время, необходимое для создания SessionFactory. Если указанный выше запрос не является первым доступом к базе данных, он ничего не сделает в этом отношении.

2) Set FetchMode необходимо применить к дочерним элементам, например:

var products = ((HandleSession)_handleSession).Session.CreateCriteria(typeof(Product))
                .SetFetchMode("ProductChildren", FetchMode.Eager)
                .List<Product>()
                .AsEnumerable();

3) Это похоже на проблему N + 1, если я правильно интерпретирую методы на скриншотах. Вы преобразуете Products в результате запроса в список ProductDTO? Если это так, то кажется, что дочерние коллекции загружаются из базы данных в цикле лениво.

Edit:

Чтобы бороться с N + 1 Select, мы должны сказать NHibernate загружать все заранее, желательно с Futures. Вот потенциальное решение, которое в основном выбирает все ваши данные из БД с помощью нескольких операторов Select. Я не включал никаких условий. Те, которые вы должны были бы добавить соответственно.

// any where-condition will have to be applied here and in the subsequent queries
var products = session.QueryOver<Product>()
    .Future();

var products2 = session.QueryOver<Product>()
    .Fetch(p => p.ProductType).Eager
    .Future();

var products3 = session.QueryOver<Product>()
    .Fetch(p => p.ProductAttributes).Eager
    .Future();

var products4 = session.QueryOver<Product>()
    .Fetch(p => p.ProductGroups).Eager
    .Future();

// Here we execute all of the above queries in one roundtrip.
// Since we already have all the data we could possibly want, there is no need
// for a N+1 Select.
return new ProductList(products.Select(p => p.ToProductContract()));
1 голос
/ 20 апреля 2011

Если вы используете этот сеанс только для отчетов, вы должны использовать сеансы без сохранения состояния: http://nhforge.org/blogs/nhibernate/archive/2008/10/30/bulk-data-operations-with-nhibernate-s-stateless-sessions.aspx

1 голос
/ 18 апреля 2011

Один из вариантов - включить размер партии в ваших коллекциях. Я предполагаю, что они ленивы, и с включенным размером пакета он будет пытаться получить коллекции для нескольких объектов за одну поездку.

Это не имеет значения, если вы выбираете 1 сущность с одной коллекцией, но может иметь огромное значение, если вы выберете 1000 сущностей, каждая из которых имеет одну коллекцию. Использование пакета размером 1000 приведет к 2 запросам вместо 1001.

Пытался найти документацию, но нашел только этот пример:

Размер партии чередующегося nhibernate

Использование стратегий соединения в вашем случае приведет к гигантским результирующим наборам, так что это не очень хороший вариант. Лучшим вариантом было бы использовать FetchMode.Select, который явно заставлял бы загружать ваши коллекции в последующем цикле.

Еще одна вещь, которая может улучшить производительность, это настройка:

Session.FlushMode = FlushMode.Never;

которые отключают автоматическую очистку вашей области. Это полезно, если все, что вы на самом деле делаете, это считываете данные, а не модифицируете их. Тем не менее, вы увидите вызовы IsDirty или любую другую проверку на наличие грязных объектов в вашем стеке вызовов.

...