如何在 nHibernate 中添加 NOLOCK?

Posted

技术标签:

【中文标题】如何在 nHibernate 中添加 NOLOCK?【英文标题】:How do add NOLOCK with nHibernate? 【发布时间】:2010-11-21 03:00:12 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

【讨论】:

但是将连接事务隔离级别设置为'read uncommitted'相当于在查询中对每个表添加(nolock),对吧? 不同之处在于我认为 NOLOCK 指定了一个特定的表,而 read uncommitted 指定了选择中的每个表。我无法想象这会经常成为问题......但也许。 @codeulike 我会反对,请参考technet.microsoft.com/en-us/library/ms187373.aspx;因为它可能有助于类似的功能,但它不一样,nolock 可以帮助您读取有时称为脏读的数据,但这有助于您在处理数百万行或记录时提高速度.. 否则为什么会选择n休眠!! 嘿,Oracle 没有 Read Uncommitted,那我该怎么做呢? @OnlyaCuriousMind Oracle 的工作方式类似于 SQL Server 的 READ_COMMITTED_SNAPSHOT msdn.microsoft.com/pt-br/library/ms173763.aspx【参考方案2】:

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

【讨论】:

这个程序动态构建一个sql。这样做缓存有什么好处?【参考方案3】:

如果您要在大量查询中使用它,可以通过配置属性connection.isolation 将其设置为默认值。

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

查看documentation on this property。

【讨论】:

这对我有用!将 web.config 条目从 ReadCommitted 更改为 ReadUncommitted 消除了 DEADLOCK 异常。我知道读取现在可能是“脏”的,但我认为这比阻止和终止用户的会话/体验更好。如果他们查看脏数据,则假定它尚未在视图上更新,并且它将在下一次页面浏览中出现。很好的解决方案。虽然它没有解决实际问题,并且页面仍然需要很长时间才能加载 - 它已经消除了 DEADLOCK 错误。【参考方案4】:

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

Session.BeginTransaction(IsolationLevel.ReadUncommitted);

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

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

【讨论】:

虽然此解决方案解决了最初的问题,但它可能会创建另一个问题:现在您有一个显式事务打开,因此在默认刷新行为下,您会突然获得额外的 CPU 使用,并可能往返于您所在位置的数据库以前没有出现过。只需要记住这一点。【参考方案5】:

你可以使用 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;
        

【讨论】:

【参考方案6】:

你可以试试这个:

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);
    

【讨论】:

【参考方案7】:

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

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

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

【讨论】:

以上是关于如何在 nHibernate 中添加 NOLOCK?的主要内容,如果未能解决你的问题,请参考以下文章

NHibernate:如何添加多个程序集?

如何让 Nhibernate 重建数据库?

如何在 NHibernate 中映射没有标识列的视图?

使用 Nolock 提示更新查询

使用 WITH(NOLOCK) 提高性能

Fluent nHibernate - 如何在联结表上映射非键列?