2009-08-19 4 views

ответ

8

Если вы собираетесь использовать его во многих своих запросах, вы можете установить его по умолчанию с помощью свойства конфигурации connection.isolation.

<property name="connection.isolation">ReadUncommitted</property> 

Проверьте documentation on this property.

+0

Это работает для меня! Изменение записи web.config из ReadCommitted в ReadUncommitted устраняет исключения DEADLOCK. Я понимаю, что чтение теперь может быть «грязным», но я думаю, что это лучшая альтернатива, чем блокирование и убийство сеанса пользователя/опыта. Если они рассматривают грязные данные, предполагается, что он еще не обновлен в представлении, и он будет отображаться на следующей странице. Хорошее решение. Хотя он не затрагивает актуальную проблему, и страницы все еще требуют много времени для загрузки - это устранило ошибки DEADLOCK. – dankeshawn

19

SetLockMode(LockMode.None) или connection.isolation ReadUncomitted НЕ добавляет NOLOCK к вашим запросам.

Ayende переходит в correct answer on his blog:

Если вы используете <sql-query> вы можете сделать следующее:

<sql-query name="PeopleByName"> 
    <return alias="person" 
        class="Person"/> 
    SELECT {person.*} 
    FROM People {person} WITH(nolock) 
    WHERE {person}.Name LIKE :name 
</sql-query> 

Обратите внимание на WTIH(nolock) прилагается к статье FROM.

+2

Но установка уровня изоляции транзакции соединения на «чтение незафиксированной» эквивалентна добавлению (nolock) к каждой таблице запроса, правильно? – codeulike

+0

Разница заключается в том, что NOLOCK задает определенную таблицу, а read uncommitted указывает каждую таблицу в select. Я не могу себе представить, что проблема была бы очень часто ... но, может быть. – PJUK

+0

@codeulike Я буду спорить с нет, см. Это http://technet.microsoft.com/en-us/library/ms187373.aspx; Поскольку это может помочь с подобной функциональностью, но это не то же самое, и nolock помогает вам читать данные, которые иногда называются грязными, но это помогает вам улучшить скорость при работе с миллионами строк или записей .. иначе зачем выбирать nHibernate !! – MarmiK

15

Я объясню, как это сделать, чтобы вы могли добавлять NOLOCK (или любые другие подсказки), в то время как все еще используете ICriteria или HQL, и не должны хранить знания своих запросов в конфигурациях сопоставлений или конфигурации сеанса.

Я написал это для NHibernate 2.1. Существует ряд серьезных предостережений, в основном из-за ошибок в NHibernate при включении «use_sql_comments» (см. Ниже). Я не уверен, были ли исправлены ошибки в NH 3, но попробуйте. ОБНОВЛЕНИЕ: Ошибки не были исправлены с NH 3.3. Техника и обходные пути, которые я описываю здесь, все еще работают.

Во-первых, создать перехватчик, как это:

[Serializable] 
public class QueryHintInterceptor : EmptyInterceptor 
{ 
    internal const string QUERY_HINT_NOLOCK_COMMENT = "queryhint-nolock: "; 

    /// <summary> 
    /// Gets a comment to add to a sql query to tell this interceptor to add 'OPTION (TABLE HINT(table_alias, INDEX = index_name))' to the query. 
    /// </summary> 
    internal static string GetQueryHintNoLock(string tableName) 
    { 
     return QUERY_HINT_NOLOCK_COMMENT + tableName; 
    } 

    public override SqlString OnPrepareStatement(SqlString sql) 
    { 
     if (sql.ToString().Contains(QUERY_HINT_NOLOCK_COMMENT)) 
     { 
      sql = ApplyQueryHintNoLock(sql, sql.ToString()); 
     } 

     return base.OnPrepareStatement(sql); 
    } 

    private static SqlString ApplyQueryHintNoLock(SqlString sql, string sqlString) 
    { 
     var indexOfTableName = sqlString.IndexOf(QUERY_HINT_NOLOCK_COMMENT) + QUERY_HINT_NOLOCK_COMMENT.Length; 

     if (indexOfTableName < 0) 
      throw new InvalidOperationException(
       "Query hint comment should contain name of table, like this: '/* queryhint-nolock: tableName */'"); 

     var indexOfTableNameEnd = sqlString.IndexOf(" ", indexOfTableName + 1); 

     if (indexOfTableNameEnd < 0) 
      throw new InvalidOperationException(
       "Query hint comment should contain name of table, like this: '/* queryhint-nlock: tableName */'"); 

     var tableName = sqlString.Substring(indexOfTableName, indexOfTableNameEnd - indexOfTableName).Trim(); 

     var regex = new Regex(@"{0}\s(\w+)".F(tableName)); 

     var aliasMatches = regex.Matches(sqlString, indexOfTableNameEnd); 

     if (aliasMatches.Count == 0) 
      throw new InvalidOperationException("Could not find aliases for table with name: " + tableName); 

     var q = 0; 
     foreach (Match aliasMatch in aliasMatches) 
     { 
      var alias = aliasMatch.Groups[1].Value; 
      var aliasIndex = aliasMatch.Groups[1].Index + q + alias.Length; 

      sql = sql.Insert(aliasIndex, " WITH (NOLOCK)"); 
      q += " WITH (NOLOCK)".Length; 
     } 
     return sql; 
    } 

    private static SqlString InsertOption(SqlString sql, string option) 
    { 
     // The original code used just "sql.Length". I found that the end of the sql string actually contains new lines and a semi colon. 
     // Might need to change in future versions of NHibernate. 
     var regex = new Regex(@"[^\;\s]", RegexOptions.RightToLeft); 
     var insertAt = regex.Match(sql.ToString()).Index + 1; 
     return sql.Insert(insertAt, option); 
    } 
} 

Затем создайте некоторые хорошие методы расширения где:

public static class NHibernateQueryExtensions 
{ 
    public static IQuery QueryHintNoLock(this IQuery query, string tableName) 
    { 
     return query.SetComment(QueryHintInterceptor.GetQueryHintNoLock(tableName)); 
    } 

    public static ICriteria QueryHintNoLock(this ICriteria query, string tableName) 
    { 
     return query.SetComment(QueryHintInterceptor.GetQueryHintNoLock(tableName)); 
    } 
} 

Далее, скажите NHibernate использовать перехватчик:

config.SetInterceptor(new QueryHintInterceptor()); 

И наконец, включите use_sql_comments в вашей конфигурации NHibernate.

И все готово! Теперь вы можете добавить NOLOCK подсказки, как это:

var criteria = Session.CreateCriteria<Foo>() 
    .QueryHintNoLock("tableFoo") 
    .List<Foo>(); 

я на основе этой работы вокруг методике, описанной здесь: http://www.codewrecks.com/blog/index.php/2011/07/23/use-sql-server-query-hints-with-nhibernate-hql-and-icriteria/

NHibernate Showstopping Bugs:

Во-первых, есть this bug с NHibernate, что вы необходимо будет исправить. (Вы можете исправить эту ошибку, исправив источник NHibernate напрямую, или by doing what I did и создав свой собственный Диалект, который исправляет проблему).

Во-вторых, есть еще одна ошибка, которая возникает, когда вы выполняете страничный запрос, на любой странице после первой страницы, и вы используете прогнозы. Sql, сгенерированный NHibernate, полностью ошибочен вокруг предложения OVER.На этом этапе я не знаю, как исправить эту ошибку, но я над этим работаю. ОБНОВЛЕНИЕ: Я подробно остановился на этой ошибке here. Как и в случае с другой ошибкой, это можно также исправить либо путем исправления исходного кода NHibernate, либо путем создания собственного класса Dialect.

+0

Эта процедура строит sql динамически. Как насчет преимуществ кеширования? – Luciano

5

Это не добавляет NOLOCK к вашим запросам, которые я могу сказать, но он должен обеспечивать ту же функциональность, которая должна выполнять грязные чтения только внутри транзакции.

Session.BeginTransaction(IsolationLevel.ReadUncommitted); 

Я использовал Sql Profiler, чтобы увидеть, что вышеприведенная команда будет делать, но это ничего не изменило о запросе или добавить NOLOCK к ним (NHibernate использует sp_executesql для большинства моих запросов). Я все равно бегал с ним, и, похоже, все тупики исчезли. Наше программное обеспечение работает уже 3 дня без взаимоблокировок. До этого изменения я обычно мог воспроизвести тупики в течение 15 минут. Я не на 100% убежден, что это исправлено, но после проверки на несколько недель я узнаю больше.

Это работает для других, а также: http://quomon.com/NHibernate-deadlock-problem-q43633.aspx

+0

Хотя это решение решает исходную проблему, оно может создать другое: теперь у вас открыто явное транзакционное открытие, поэтому при поведении по умолчанию вы внезапно получаете дополнительное использование ЦП и, возможно, обратные вызовы к БД где-то там, где раньше они не казались. Просто нужно помнить об этом. –

0

Вы можете решить с помощью перехватчик.

var session = SessionFactory.OpenSession(new NoLockInterceptor()); 

Вот реализация класса NoLockInterceptor. В основном класс NoLockInterceptor будет вставлять подсказку «WITH (NOLOCK)» после каждого имени таблицы в запросе выбора, генерируемого nHibernate.


public class NoLockInterceptor : EmptyInterceptor 
{ 
    public override SqlString OnPrepareStatement(SqlString sql) 
     { 
      //var log = new StringBuilder(); 
      //log.Append(sql.ToString()); 
      //log.AppendLine(); 

      // Modify the sql to add hints 
      if (sql.StartsWithCaseInsensitive("select")) 
      { 
       var parts = sql.ToString().Split().ToList(); 
       var fromItem = parts.FirstOrDefault(p => p.Trim().Equals("from", StringComparison.OrdinalIgnoreCase)); 
       int fromIndex = fromItem != null ? parts.IndexOf(fromItem) : -1; 
       var whereItem = parts.FirstOrDefault(p => p.Trim().Equals("where", StringComparison.OrdinalIgnoreCase)); 
       int whereIndex = whereItem != null ? parts.IndexOf(whereItem) : parts.Count; 

       if (fromIndex == -1) 
        return sql; 

       parts.Insert(parts.IndexOf(fromItem) + 3, "WITH (NOLOCK)"); 
       for (int i = fromIndex; i < whereIndex; i++) 
       { 
        if (parts[i - 1].Equals(",")) 
        { 
         parts.Insert(i + 3, "WITH (NOLOCK)"); 
         i += 3; 
        } 
        if (parts[i].Trim().Equals("on", StringComparison.OrdinalIgnoreCase)) 
        { 
         parts[i] = "WITH (NOLOCK) on"; 
        } 
       } 
       // MUST use SqlString.Parse() method instead of new SqlString() 
       sql = SqlString.Parse(string.Join(" ", parts)); 
      } 

      //log.Append(sql); 
      return sql; 
     } 
}