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
ICriteria または HQL を使用しながら、クエリの知識をマッピングまたはセッション ファクトリ構成に貼り付ける必要なく、NOLOCK (またはその他のクエリ ヒント) を追加できるようにする方法を説明します。
これは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>();
ここで説明されている手法に基づいて、この作業を行いました。基準/
NHibernate の重大なバグ:
まず、修正が必要な NHibernateのバグがあります。(このバグは、NHibernate ソースを直接修復するか、私が行ったことを実行して問題を修復する独自の Dialect を作成することで修正できます)。
第二に、最初のページの後の任意のページでページ クエリを実行し、プロジェクションを使用している場合に発生するように見える別のバグがあります。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% 確信しているわけではありませんが、さらに 1 週間分のテストを行った後、詳細がわかるでしょう。
これは他の人にも有効です: http://quomon.com/NHibernate-deadlock-problem-q43633.aspx
Interceptor を使用することで解決できます。
var session = SessionFactory.OpenSession(new NoLockInterceptor());
NoLockInterceptor クラスの実装を次に示します。基本的に、NoLockInterceptor クラスは、nHibernate によって生成された選択クエリの各テーブル名の後に "WITH (NOLOCK)" ヒントを挿入します。
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;
}
}