【问题标题】:Long commit results in slow performance of nHibernate长时间提交会导致 nHibernate 性能下降
【发布时间】:2013-06-13 23:22:54
【问题描述】:

我们在一个 ASP Web 应用程序中使用 nHibernate 并使用每请求会话模式。用户能够执行的某些操作会导致数百个 INSERT 和 UPDATE 语句,这些语句需要几秒钟才能完成。这会导致性能下降,因为在将整个事务提交到数据库之前,用户无法看到其操作的结果。

顺便说一句,之前我们将结果保存在 Session 中,但这也不是一个好的解决方案。将内容写入 Session 会导致读写锁,从而导致按顺序处理并发请求。因此,长请求会阻塞其他请求,直到完成。这就是我们转而将结果写入数据库的原因,但正如我所解释的,这会带来一系列问题。

我们如何解决这个问题?我只能想到在后台提交结果时以某种方式将结果 bakc 发送到浏览器。如果可能的话,我不知道会话如何与来自浏览器的请求耦合。但也许有可能?或者有没有可行的解决方案?

编辑

  • 我们正在使用 Guid.Comb 标识符策略
  • 了解提交的大小:单个提交涉及超过 2000 个 INSERT 和 UPDATE 语句,并且在操作运行期间发出类似数量的 SELECT 语句。提交需要 2-4 分钟才能完成(有那么长吗?)。

请问我是否没有提供足够的信息。我真的不知道现在在这里写什么有趣的东西,因为我不知道我应该在哪个方向寻找解决方案。

【问题讨论】:

    标签: sql-server asp.net-mvc nhibernate


    【解决方案1】:

    我在这里写过这个问题:

    http://www.philliphaydon.com/2011/09/the-benefits-of-letting-the-orm-generate-the-identity-part-1/

    如果您使用在数据库中生成的IDENTITYGUIDs,您将遇到插入速度慢和无法批处理的问题。

    当使用 GUID 插入 SQL 时,NHibernate 会首先发出获取新 GUID 的请求。

    你最终会得到这样的交易:

    - statement #1
    begin transaction with isolation level: Unspecified
    
    - statement #2
    select newid()
    
    - statement #3
    select newid()
    
    - statement #4
    select newid()
    
    - statement #5
    INSERT INTO Fruit
                (Name,
                 Id)
    VALUES      ('Apple0' /* @p0_0 */,
                 '269bc638-74b4-4568-85d1-45b6e537fcbd' /* @p1_0 */)
    
    INSERT INTO Fruit
                (Name,
                 Id)
    VALUES      ('Apple1' /* @p0_1 */,
                 'fc848779-b173-4c31-b8b6-0a7735c0c2dc' /* @p1_1 */)
    
    INSERT INTO Fruit
                (Name,
                 Id)
    VALUES      ('Apple2' /* @p0_2 */,
                 '232c8971-18c7-486d-9152-26c969c3b632' /* @p1_2 */)
    
    - statement #6
    commit transaction
    

    同样,当您使用 IDENTITY 时,NHibernate 需要从数据库中选择新的 Id 来更新模型,当 NHibernate 需要在插入之前将这个新对象与另一个对象关联时,这一点尤其重要。

    使用 IDENTITY 最终会得到如下所示的事务:

    - statement #1
    begin transaction with isolation level: Unspecified
    
    - statement #2
    INSERT INTO People
                (FirstName,
                 Surname)
    VALUES      ('Phillip0' /* @p0 */,
                 'Haydon' /* @p1 */);
    
    select SCOPE_IDENTITY()
    
    - statement #3
    INSERT INTO People
                (FirstName,
                 Surname)
    VALUES      ('Phillip1' /* @p0 */,
                 'Haydon' /* @p1 */);
    
    select SCOPE_IDENTITY()
    
    - statement #4
    INSERT INTO People
                (FirstName,
                 Surname)
    VALUES      ('Phillip2' /* @p0 */,
                 'Haydon' /* @p1 */);
    
    select SCOPE_IDENTITY()
    
    - statement #5
    commit transaction
    

    在一台相当不错的计算机上运行一些基本测试,我得到了插入结果,时间如下:

    • 身份 28951 毫秒
    • 新的 30241 毫秒

    更新它以使用 HiLo 或 GuidComb,允许 NHibernate 自己生成身份。

    使用 HiLo,NHibernate 会发出下一个 hi 值的请求,然后更新 hi 值,一旦有 hi 就可以生成一系列 Id,直到 lo 满为止。

    这导致插入如下:

    - statement #1
    begin transaction with isolation level: Unspecified
    
    - statement #2
    Reading high value: 
    select next_hi
    from   hibernate_unique_key with (updlock, rowlock)
    
    - statement #3
    Updating high value: 
    update hibernate_unique_key
    set    next_hi = 3 /* @p0 */
    where  next_hi = 2 /* @p1 */
    
    - statement #4
    INSERT INTO People
                (FirstName,
                 Surname,
                 Id)
    VALUES      ('Phillip0' /* @p0_0 */,
                 'Haydon' /* @p1_0 */,
                 202 /* @p2_0 */)
    
    INSERT INTO People
                (FirstName,
                 Surname,
                 Id)
    VALUES      ('Phillip1' /* @p0_1 */,
                 'Haydon' /* @p1_1 */,
                 203 /* @p2_1 */)
    
    INSERT INTO People
                (FirstName,
                 Surname,
                 Id)
    VALUES      ('Phillip2' /* @p0_2 */,
                 'Haydon' /* @p1_2 */,
                 204 /* @p2_2 */)
    
    - statement #5
    commit transaction
    

    同样使用 GuidComb,NHibernate 会为你生成一个 GUID:

    - statement #1
    begin transaction with isolation level: Unspecified
    
    - statement #2
    INSERT INTO Fruit
                (Name,
                 Id)
    VALUES      ('Apple0' /* @p0_0 */,
                 'db902160-edbb-49c7-bf52-9f660018299a' /* @p1_0 */)
    
    INSERT INTO Fruit
                (Name,
                 Id)
    VALUES      ('Apple1' /* @p0_1 */,
                 '5e852528-3a6f-41d2-a6b1-9f660018299a' /* @p1_1 */)
    
    INSERT INTO Fruit
                (Name,
                 Id)
    VALUES      ('Apple2' /* @p0_2 */,
                 '2f63c6e8-e595-4393-ad15-9f660018299a' /* @p1_2 */)
    
    - statement #3
    commit transaction
    

    这意味着 NHibernate 在进行插入时不需要往返数据库,这两者都允许发生批量插入,正如您在 HiLo 和 GuidComb 中看到的那样,只有 1 条语句被发送到服务器,不像 NEWID /IDENTITY 为每个插入发送一条语句,或为每个新 id 进行选择。

    这会大大缩短时间,例如:

    • HiLo 9287ms
    • GuidComb 9060ms

    希望对你有帮助:)

    【讨论】:

    • 我们已经在使用 GuidComb,所以这不是我们可以改进的地方。
    【解决方案2】:

    您尚未发布任何代码,但在大多数情况下,您可以在 NHibernate 中使用批处理获得更好的性能。设置更大的批量大小将减少到数据库的往返次数。但是你可能需要重写一些代码。

    <property name="adonet.batch_size">1000</property>
    

    将配置您的批量大小。

    第二个选项是使用 IStatelessSession 而不是 ISession。你没有任何状态,虽然性能可以提高。

    【讨论】:

    • 我认为将批量设置大也会对检索性能产生负面影响。在这种情况下,无状态会话不是一个选项,因为这意味着我们会丢失大部分 INSERT 和 UPDATE 语句的级联。
    猜你喜欢
    • 1970-01-01
    • 2012-04-17
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多