2013-10-15 4 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(); 
    } 
} 

Я ударил эту услугу 10 раз concurrenly с помощью 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() указывает идентификатор неправильной строки, поэтому два запроса обновляют одну и ту же строку.

Вкратце: это полный беспорядок, и это явно разделяет соединение БД между запросами.

Вопрос: Почему? Как мне настроить данные так, чтобы каждый запрос получил свое собственное соединение из пула соединений?

ответ

3

Наконец-то решил, что за день!

Источником проблемы NHibernate-х connection release mode:

11,7. Подключение Режимы Release

поведение наследство (1.0.x) из NHibernate относительно управления соединением ADO.NET было то, что ISession получит соединение , когда он был первым необходимо, а затем провести к этой связи до сессии был закрыт. NHibernate представил понятие подключения режимы выпуска, чтобы сообщить сеансу, как обрабатывать его соединения ADO.NET. ... Различные режимы высвобождения определены перечисленных значений NHibernate.ConnectionReleaseMode:

  • OnClose - это, по существу наследство поведение, описанное выше. Сеанс NHibernate получает соединение, когда ему сначала необходимо выполнить некоторый доступ к базе данных и удерживать его до тех пор, пока сеанс не будет закрыт.

  • AfterTransaction - говорит освободить связи после того, как NHibernate.ITransaction завершена.

Параметр конфигурации hibernate.connection.release_mode используется , чтобы указать, какой режим выпуска для использования.

...

  • after_transaction - говорит использовать ConnectionReleaseMode.AfterTransaction. Обратите внимание, что при ConnectionReleaseMode.AfterTransaction, если сеанс считается в режиме автоматической фиксации (т. Е. Никакой транзакции не было начато), соединения будут выпущены после каждой операции.

Это запутался вместе с пулы соединений MySQL .NET/Connector по умолчанию, а также эффективно означает, что соединения были заменены между параллельными запросами, так как один запрос выпустила соединение обратно в пул, а другой приобрел его.

Однако, я думаю, что тот факт, что NHibernate называет LAST_INSERT_ID() после освобождения и повторного приобретения соединения, является ошибкой. Он должен вызывать LAST_INSERT_ID() внутри той же «операции».

Во всяком случае, решения:

  1. Используйте операции, которая является то, что мы обычно делаем, или
  2. Если вы не можете или не хотите использовать транзакции в определенном контексте по какой-то причине (что и случилось сегодня), установите режим освобождения соединения на «on close». С FluentNHibernate что бы:

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

    И отсюда на связи привязывается к сессии, даже если нет сделки.

Смежные вопросы