2013-10-15 69 views
1

我們似乎發現了一個奇怪的問題,其中兩個對我們的服務的併發請求實際上使用相同的數據庫連接。ServiceStack/FluentNHibernate/MySQL - 兩個併發請求使用相同的連接

我們的設置是ServiceStack + NHibernate + FluentNHibernate + MySQL。我已成立了一個小測試,再現問題:

public class AppHost : AppHostBase 
{ 
    private ISessionFactory _sessionFactory; 

    public AppHost() : base("Lala Service", typeof(AppHost).Assembly) 
    { 
    } 

    public override void Configure(Container container) 
    { 
     _sessionFactory = Fluently.Configure() 

      .Database(MySQLConfiguration.Standard.ConnectionString(conn => 
       conn.Server("localhost").Username("lala").Password("lala").Database("lala"))) 

      .Mappings(mappings => mappings.AutoMappings.Add(
       AutoMap.Assembly(GetType().Assembly).Where(t => t == typeof(Lala)) 
         .Conventions.Add(DefaultLazy.Never(), DefaultCascade.All()))) 

     .BuildSessionFactory(); 

     container.Register(c => _sessionFactory.OpenSession()).ReusedWithin(ReuseScope.Request); 
    } 
} 

public class Lala 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

[Route("/lala")] 
public class LalaRequest 
{ 
} 

public class LalaReseponse 
{ 
} 

public class LalaService : Service 
{ 
    private ISession _session; 

    public ISession Session1 
    { 
     get { return _session; } 
     set { _session = value; } 
    } 

    public LalaReseponse Get(LalaRequest request) 
    { 
     var lala = new Lala 
     { 
      Name = Guid.NewGuid().ToString() 
     }; 

     _session.Persist(lala); 
     _session.Flush(); 

     lala.Name += " XXX"; 

     _session.Flush(); 

     return new LalaReseponse(); 
    } 
} 

的我打這個服務concurrenly 10次通過Ajax像這樣:

<script type="text/javascript"> 
     for (i = 0; i < 10; i++) { 
      console.log("aa"); 
      $.ajax({ 
       url:  '/lala', 
       dataType: 'json', 
       cache: false 
      }); 
     } 
    </script> 

結果一致:

  1. 打開的連接數< 10.
  2. 並非所有記錄都已更新。
  3. 有時 - 拋出StaleObjectStateException - 如果我刪除記錄。

這背後的原因是連接被兩個併發請求重用,然後LAST_INSERT_ID()給出了錯誤行的ID,所以兩個請求正在更新同一行。

簡而言之:它是一個完整的混亂,它清楚地共享請求之間的數據庫連接。

問題是:爲什麼?我應該如何配置這些東西,以便每個請求都能從連接池中獲得自己的連接?

回答

3

終於解決了它,什麼一天浪費!

問題的根源是NHibernate的connection release mode

11.7。連接釋放

問候的NHibernate的遺產(1.0.x的)行爲ADO.NET 連接管理是一個ISession的將獲得一個連接 首先需要的時候,再保持對這一方面,直到 會議關門了。 NHibernate引入了連接 發佈模式的概念,以告訴會話如何處理其ADO.NET連接。 ... 的不同的釋放模式通過 NHibernate.ConnectionReleaseMode的枚舉值來:

  • 的OnClose - 基本上是以上描述的傳統的行爲。當NHibernate會話第一次需要執行 某些數據庫訪問並持有該連接,直到關閉會話 時,NHibernate會話才獲得連接。

  • AfterTransaction - 說釋放連接後,NHibernate.ITransaction已完成 。

配置參數hibernate.connection.release_mode用於 來指定要使用的釋放模式。

...

  • after_transaction - 說,使用 ConnectionReleaseMode.AfterTransaction。 請注意,通過 ConnectionReleaseMode.AfterTransaction,如果會話被認爲是 處於自動提交模式(即沒有事務已啓動),則連接 將在每次操作後發佈。

這引起了與MySQL .NET /連接器的默認連接池糾纏在了一起,並有效地意味着連接被交換併發請求之間,爲一個請求釋放的連接返回到池和其他收購了它。

但是,我認爲NHibernate在發佈和重新獲取連接後調用LAST_INSERT_ID()的事實是一個錯誤。它應該在相同的「操作」內調用LAST_INSERT_ID()

總之,解決方案:

  1. 使用事務,這就是我們通常做的,或
  2. 如果您不能或不想使用交易在一定的語境中由於某種原因(這是今天發生的事情),將連接釋放模式設置爲「on close」。如果使用FluentNHibernate:

    .ExposeConfiguration(cfg => 
        cfg.SetProperty("connection.release_mode", "on_close")); 
    

    並且從這裏開始,即使沒有事務,連接也會綁定到會話。

相關問題