使用nhibernate时如何添加NOLOCK?(条件查询)
7 回答
SetLockMode(LockMode.None)
或connection.isolation ReadUncomitted
不将 a 添加NOLOCK
到您的查询中。
Ayende在他的博客上给出了正确答案:
如果您正在使用<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_comments”时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_comments属性。
你完成了!现在您可以像这样添加 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-标准/
NHibernate Showstopping Bug:
首先,您需要修复 NHibernate的这个错误。(您可以通过直接修复 NHibernate 源来修复此错误,或者通过执行我所做的并创建您自己的方言来修复此问题)。
其次,当您在第一页之后的任何页面上执行分页查询时,似乎还会出现另一个错误,并且您正在使用投影。NHibernate 生成的 sql 围绕“OVER”子句是完全错误的。在这个阶段,我不知道如何修复这个错误,但我正在努力。更新:我在这里详细介绍了如何修复此错误。与其他错误一样,这个错误也可以通过修复 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
您可以使用拦截器来解决它。
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;
}
这样,它将不对查询中的所有表和内部连接添加任何锁。
这对使用工作单元模式的所有人都有好处。