28

使用nhibernate时如何添加NOLOCK?(条件查询)

4

7 回答 7

23

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子句。

于 2010-11-18T12:21:22.747 回答
19

我将解释如何执行此操作,以便您可以添加 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 类来修复。

于 2011-12-12T04:59:15.280 回答
8

如果您打算在很多查询中使用它,您可以通过配置属性将其设置为默认值connection.isolation

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

查看有关此属性的文档

于 2009-11-23T21:41:24.963 回答
6

我可以告诉您,这不会将 NOLOCK 添加到您的查询中,但它应该提供相同的功能 - 即仅在事务内部执行脏读。

Session.BeginTransaction(IsolationLevel.ReadUncommitted);

我使用 Sql Profiler 来查看上面的命令会做什么,但它没有改变查询的任何内容或向它们添加 NOLOCK(nhibernate 对我的大多数查询使用 sp_executesql)。无论如何我都跑了,似乎所有的僵局都消失了。我们的软件已经以这种方式运行了 3 天,没有出现死锁。在此更改之前,我通常可以在 15 分钟内重现死锁。我不是 100% 相信这修复了它,但经过几周的测试,我会知道更多。

这对其他人也有效:http: //quomon.com/NHibernate-deadlock-problem-q43633.aspx

于 2013-01-18T21:00:15.723 回答
2

您可以使用拦截器来解决它。

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;
        }
}
于 2016-09-15T18:33:44.470 回答
1

你可以试试这个:

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);
    }
}
于 2018-10-15T08:13:35.437 回答
0

我已经接受了@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;
    }

这样,它将不对查询中的所有表和内部连接添加任何锁。

这对使用工作单元模式的所有人都有好处。

于 2018-12-02T10:37:23.620 回答