ServiceStack / FluentNHibernate / MySQL - 两个并发请求使用的相同连接
本文关键字:请求 连接 并发 MySQL FluentNHibernate ServiceStack 两个 | 更新日期: 2023-09-27 18:35:29
我们似乎遇到了一个奇怪的问题,其中对我们服务的两个并发请求实际上使用相同的数据库连接。
我们的设置是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>
结果一致为:
- 打开的连接数<10。
- 并非所有记录都已更新。
- 有时 - 抛出
StaleObjectStateException
- 如果我删除记录。
这背后的原因是连接被两个并发请求重用,然后 LAST_INSERT_ID() 给出错误行的 ID,因此两个请求正在更新同一行。
简而言之:这完全是一团糟,它显然在请求之间共享数据库连接。
问题是:为什么?我应该如何配置内容,以便每个请求从连接池获得自己的连接?
终于解决了,真是浪费一天!
问题的根源是NHibernate的连接释放模式:
11.7. 连接释放模式
NHibernate关于 ADO.NET 的遗留(1.0.x)行为 连接管理是 ISession 将获得连接 当第一次需要它时,然后保持该连接,直到 会议已关闭。NHibernate引入了连接的概念 释放模式,用于告知会话如何处理其 ADO.NET 连接。 ... 不同的释放模式由 NHibernate.ConnectionReleaseMode:
OnClose - 本质上是上述遗留行为。这 NHibernate 会话在首次需要执行时获取连接 某些数据库访问并保留该连接直到会话 已关闭。
交易后 - 说在 NHibernate.ITransaction已完成。
使用配置参数hibernate.connection.release_mode 以指定要使用的发布模式。
。
- after_transaction - 说使用 ConnectionReleaseMode.AfterTransaction. 请注意,使用 ConnectionReleaseMode.AfterTransaction,如果会话被视为 处于自动提交模式(即未启动任何事务)连接 将在每次操作后释放。
这与MySQL .NET/Connector的默认连接池纠缠在一起,实际上意味着连接在并发请求之间交换,因为一个请求将连接释放回池,另一个请求获取连接。
但是,我认为NHibernate在释放并重新获取连接后调用LAST_INSERT_ID()
的事实是一个错误。它应该在同一"操作"中调用LAST_INSERT_ID()
。
无论如何,解决方案:
- 使用交易,这是我们通常所做的,或者
-
如果由于某种原因不能或不想在特定上下文中使用事务(这就是今天使用的情况),请将连接释放模式设置为"关闭时"。使用FluentNHibernate,这将是:
.ExposeConfiguration(cfg => cfg.SetProperty("connection.release_mode", "on_close"));
从这里开始,即使没有事务,连接也会绑定到会话。