Nhibernateを使用するときにNOLOCKをどのように追加しますか? (基準クエリ)
SetLockMode(LockMode.None)
または _connection.isolation ReadUncomitted
_ はクエリに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>
_
FROM
句に追加されたWTIH(nolock)
に注意してください。
ICriteriaまたはHQLを使用しながら、クエリの知識をマッピングまたはセッションファクトリ構成に固定することなく、NOLOCK(またはその他のクエリヒント)を追加できるようにする方法を説明します。
これはNHibernate2.1用に作成しました。主に「use_sql_comments」がオンになっているときのNHibernateのバグが原因で、いくつかの主要な警告があります(以下を参照)。これらのバグがNH3で修正されているかどうかはわかりませんが、試してみてください。 UPDATE:NH3.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-icriteria /
NHibernate Showstopping Bugs:
まず、修正が必要なNHibernateの このバグ があります。 (このバグは、NHibernateソースを直接修復するか、 私が行ったことを実行して そして問題を修復する独自の方言を作成することで修正できます)。
次に、最初のページの後の任意のページでページクエリを実行し、プロジェクションを使用しているときに発生するように見える別のバグがあります。 NHibernateによって生成されたSQLは、「OVER」句の前後で完全に間違っています。この段階では、このバグを修正する方法がわかりませんが、現在取り組んでいます。 UPDATE:このバグを修正する方法を詳しく説明しました ここ 。他のバグと同様に、これもNHibernateソースコードを修復するか、独自のDialectクラスを作成することで修正できます。
多くのクエリで使用する場合は、構成プロパティconnection.isolation
を使用してデフォルトとして設定できます。
<property name="connection.isolation">ReadUncommitted</property>
このプロパティに関するドキュメント を確認してください。
これは、私が知ることができるクエリにNOLOCKを追加しませんが、同じ機能を提供する必要があります。つまり、トランザクション内でのみダーティ読み取りを実行します。
Session.BeginTransaction(IsolationLevel.ReadUncommitted);
上記のコマンドが何をするかを確認するためにSqlProfilerを使用しましたが、クエリについて何も変更せず、NOLOCKを追加しませんでした(nhibernateはほとんどのクエリにsp_executesqlを使用します)。とにかくそれで走りました、そして、すべての行き詰まりがなくなったように見えます。私たちのソフトウェアは、デッドロックなしで3日間このように実行されています。この変更の前は、通常15分以内にデッドロックを再現できました。私はこれがそれを修正したと100%確信しているわけではありませんが、さらに数週間のテストの後、私はもっと知るでしょう。
これは他の人にも同様に機能しました: http://quomon.com/NHibernate-deadlock-problem-q43633.aspx
インターセプターを使用して解決できます。
var session = SessionFactory.OpenSession(new NoLockInterceptor());
これがNoLockInterceptorクラスの実装です。基本的に、NoLockInterceptorクラスは、nHibernateによって生成されたselectクエリの各テーブル名の後に「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;
}
}
あなたはこれを試すことができます:
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;
}
このようにして、クエリ内のすべてのテーブルと内部結合にロックを追加しません。
これは、作業単位パターンを使用しているすべての人に適しています。