Как вы добавляете NOLOCK при использовании nhibernate? (запрос критериев)Как добавить NOLOCK с nHibernate?
ответ
Если вы собираетесь использовать его во многих своих запросах, вы можете установить его по умолчанию с помощью свойства конфигурации connection.isolation
.
<property name="connection.isolation">ReadUncommitted</property>
Проверьте documentation on this property.
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
.
Но установка уровня изоляции транзакции соединения на «чтение незафиксированной» эквивалентна добавлению (nolock) к каждой таблице запроса, правильно? – codeulike
Разница заключается в том, что NOLOCK задает определенную таблицу, а read uncommitted указывает каждую таблицу в select. Я не могу себе представить, что проблема была бы очень часто ... но, может быть. – PJUK
@codeulike Я буду спорить с нет, см. Это http://technet.microsoft.com/en-us/library/ms187373.aspx; Поскольку это может помочь с подобной функциональностью, но это не то же самое, и nolock помогает вам читать данные, которые иногда называются грязными, но это помогает вам улучшить скорость при работе с миллионами строк или записей .. иначе зачем выбирать nHibernate !! – MarmiK
Я объясню, как это сделать, чтобы вы могли добавлять 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.
Эта процедура строит sql динамически. Как насчет преимуществ кеширования? – Luciano
Это не добавляет NOLOCK к вашим запросам, которые я могу сказать, но он должен обеспечивать ту же функциональность, которая должна выполнять грязные чтения только внутри транзакции.
Session.BeginTransaction(IsolationLevel.ReadUncommitted);
Я использовал Sql Profiler, чтобы увидеть, что вышеприведенная команда будет делать, но это ничего не изменило о запросе или добавить NOLOCK к ним (NHibernate использует sp_executesql для большинства моих запросов). Я все равно бегал с ним, и, похоже, все тупики исчезли. Наше программное обеспечение работает уже 3 дня без взаимоблокировок. До этого изменения я обычно мог воспроизвести тупики в течение 15 минут. Я не на 100% убежден, что это исправлено, но после проверки на несколько недель я узнаю больше.
Это работает для других, а также: http://quomon.com/NHibernate-deadlock-problem-q43633.aspx
Хотя это решение решает исходную проблему, оно может создать другое: теперь у вас открыто явное транзакционное открытие, поэтому при поведении по умолчанию вы внезапно получаете дополнительное использование ЦП и, возможно, обратные вызовы к БД где-то там, где раньше они не казались. Просто нужно помнить об этом. –
Вы можете решить с помощью перехватчик.
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;
}
}
Это работает для меня! Изменение записи web.config из ReadCommitted в ReadUncommitted устраняет исключения DEADLOCK. Я понимаю, что чтение теперь может быть «грязным», но я думаю, что это лучшая альтернатива, чем блокирование и убийство сеанса пользователя/опыта. Если они рассматривают грязные данные, предполагается, что он еще не обновлен в представлении, и он будет отображаться на следующей странице. Хорошее решение. Хотя он не затрагивает актуальную проблему, и страницы все еще требуют много времени для загрузки - это устранило ошибки DEADLOCK. – dankeshawn