2

我们似乎遇到了一个奇怪的问题,对我们服务的两个并发请求实际上使用了同一个数据库连接。

我们的设置是 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();
    }
}

我通过 Ajax 并发访问了该服务 10 次,如下所示:

    <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,因此两个请求正在更新同一行。

简而言之:这完全是一团糟,它显然在请求之间共享数据库连接。

问题是:为什么?我应该如何配置,以便每个请求从连接池中获得自己的连接?

4

1 回答 1

4

终于解决了,真是浪费了一天!

问题的根源在于NHibernate的连接释放模式

11.7。连接释放模式

NHibernate 在 ADO.NET 连接管理方面的传统 (1.0.x) 行为是 ISession 在第一次需要时获取连接,然后保持该连接直到会话关闭。NHibernate 引入了连接释放模式的概念来告诉会话如何处理其 ADO.NET 连接。... 不同的释放模式由 NHibernate.ConnectionReleaseMode 的枚举值标识:

  • OnClose - 本质上是上述的遗留行为。NHibernate 会话在它第一次需要执行一些数据库访问时获得一个连接并保持该连接直到会话关闭。

  • AfterTransaction - 表示在 NHibernate.ITransaction 完成后释放连接。

配置参数hibernate.connection.release_mode用于指定使用哪种释放模式。

...

  • after_transaction - 表示使用 ConnectionReleaseMode.AfterTransaction。请注意,使用 ConnectionReleaseMode.AfterTransaction,如果会话被认为处于自动提交模式(即没有启动事务),则每次操作后都会释放连接

这与 MySQL .NET/Connector 的默认连接池纠缠在一起,实际上意味着连接在并发请求之间交换,因为一个请求将连接释放回池,而另一个请求获取它。

但是,我认为 NHibernateLAST_INSERT_ID()在释放并重新获取连接后调用的事实是一个错误。它应该LAST_INSERT_ID()在同一个“操作”内部调用。

无论如何,解决方案:

  1. 使用交易,这是我们通常做的,或者
  2. 如果由于某种原因(这就是今天发生的事情)您不能或不想在特定上下文中使用事务,请将连接释放模式设置为“关闭时”。使用 FluentNHibernate 将是:

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

    从这里开始,即使没有事务,连接也会绑定到会话。

于 2013-10-15T16:24:01.770 回答