【发布时间】:2010-11-21 03:00:12
【问题描述】:
在使用 nhibernate 时如何添加 NOLOCK? (条件查询)
【问题讨论】:
标签: nhibernate fluent-nhibernate criteria
在使用 nhibernate 时如何添加 NOLOCK? (条件查询)
【问题讨论】:
标签: nhibernate fluent-nhibernate criteria
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(或任何其他查询提示),同时仍使用 ICriteria 或 HQL,而无需将您的查询知识粘贴到映射或会话工厂配置中。
我为 NHibernate 2.1 编写了这个。有一些主要的警告,主要是由于打开“use_sql_cmets”时NHibernate中的错误(见下文)。我不确定这些错误是否已在 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());
最后,在您的 NHibernate 配置中启用 use_sql_cmets 属性。
你就完成了!现在您可以像这样添加 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 Bug:
首先,您需要修复带有 NHibernate 的 this bug。 (您可以通过直接修复 NHibernate 源来修复此错误,或者by doing what I did 并创建您自己的方言来修复此问题)。
其次,当您在第一页之后的任何页面上执行分页查询并且您正在使用投影时,似乎还会出现另一个错误。 NHibernate 生成的 sql 围绕“OVER”子句是完全错误的。在这个阶段,我不知道如何修复这个错误,但我正在努力。 更新:我已经详细说明了如何修复这个错误here。与其他错误一样,这个错误也可以通过修复 NHibernate 源代码或创建您自己的 Dialect 类来修复。
【讨论】:
如果您要在大量查询中使用它,可以通过配置属性connection.isolation 将其设置为默认值。
<property name="connection.isolation">ReadUncommitted</property>
【讨论】:
据我所知,这不会将 NOLOCK 添加到您的查询中,但它应该提供相同的功能 - 即仅在事务内执行脏读。
Session.BeginTransaction(IsolationLevel.ReadUncommitted);
我使用 Sql Profiler 来查看上面的命令会做什么,但它并没有改变查询的任何内容或向它们添加 NOLOCK(对于我的大多数查询,nhibernate 使用 sp_executesql)。无论如何我都跑了,似乎所有的僵局都消失了。我们的软件已经以这种方式运行了 3 天,没有出现死锁。在此更改之前,我通常可以在 15 分钟内重现死锁。我不是 100% 相信这解决了它,但经过几周的测试,我会知道更多。
这对其他人也有效:http://quomon.com/NHibernate-deadlock-problem-q43633.aspx
【讨论】:
你可以使用 Interceptor 解决它。
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;
}
}
【讨论】:
你可以试试这个:
public class NoLockInterceptor : EmptyInterceptor
{
/// <summary>
/// OnPrepare.
/// </summary>
/// <param name="sql">Query.</param>
public override SqlString OnPrepareStatement(SqlString sql)
{
var begin = SqlString.Parse("with query as (");
var end = SqlString.Parse(") select * from query with ( nolock )");
return base.OnPrepareStatement(begin + sql + end);
}
}
【讨论】:
我接受了@cbp 的回答并稍作改动:
private static SqlString ApplyQueryHintNoLock(SqlString sql)
{
var sqlString = sql.ToString();
if (_cache.Get(sqlString) is SqlString cachedSql)
{
//return cachedSql;
}
var regex1 = new Regex(@" FROM\s+[a-zA-Z1-9_.]*\s+([a-zA-Z1-9_.]*)", RegexOptions.IgnoreCase);
var regex2 = new Regex(@"(?: INNER JOIN| LEFT OUTER JOIN)\s+[a-zA-Z1-9_.]*\s+([a-zA-Z1-9_.]*) ON", RegexOptions.IgnoreCase);
var tableAliasMatches = regex1.Matches(sqlString);
var joinsAliasMatches = regex2.Matches(sqlString);
var combined = tableAliasMatches.OfType<Match>()
.Concat(joinsAliasMatches.OfType<Match>())
.Where(m => m.Success)
.OrderBy(m=>m.Index);
var noLockLength = " WITH (NOLOCK)".Length;
var q = 0;
foreach (Match aliasMatch in combined)
{
var alias = aliasMatch.Groups[1].Value;
var aliasIndex = aliasMatch.Groups[1].Index + q + alias.Length;
sql = sql.Insert(aliasIndex, " WITH (NOLOCK)");
q += noLockLength;
}
_cache.Set(sqlString, sql, DateTimeOffset.Now.AddHours(3));
return sql;
}
internal static string GetQueryHintNoLock()
{
return _queryHintNoLockCommentString;
}
这样它不会对查询中的所有表和内部连接添加锁。
这对所有使用工作单元模式的人都有好处。
【讨论】: