استفاده از NOLOCK در EF Core برای بهینهسازی عملکرد در SQL Server
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۲/۰۹ ۰۸:۰۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
TransactionScope در داتنت است. این کلاس امکان تنظیم سطح ایزولاسیون تراکنش را فراهم میکند. کد زیر نمونهای از این روش را نشان میدهد:using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted }))
{
using (var context = new MyDbContext())
{
var products = context.Products
.Where(p => p.CategoryId == categoryId)
.ToList();
}
scope.Complete();
}TransactionScope با سطح ایزولاسیون ReadUncommitted تنظیم شده است. این تنظیم به EF Core دستور میدهد که پرسوجوها را بدون در نظر گرفتن قفلهای مشترک اجرا کند، که معادل رفتار NOLOCK است. نکته مهم این است که باید متد Complete فراخوانی شود تا تراکنش بهدرستی پایان یابد. این روش ساده و قابلاعتماد است، اما ممکن است برای پروژههایی که تراکنشهای پیچیده دارند، سربار (Overhead) ایجاد کند.public class WithNoLockSqlInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
command.CommandText = AddNoLockToSql(command.CommandText);
return result;
}
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
command.CommandText = AddNoLockToSql(command.CommandText);
return new ValueTask<InterceptionResult<DbDataReader>>(result);
}
private static string AddNoLockToSql(string commandText)
{
const string pattern = @"((FROM|JOIN)\s+\[?\w+\]?\s+AS\s+\[?\w+\]?)";
const string replacement = "$1 WITH(NOLOCK)";
return Regex.Replace(commandText, pattern, replacement, RegexOptions.IgnoreCase);
}
}DbContext ثبت کنید:services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(connectionString)
.AddInterceptors(new WithNoLockSqlInterceptor()));WITH (NOLOCK)، یک تگ با نام WNL تعریف میکنیم:public static class QueryableExtensions
{
public const string WithNoLockTag = "WNL";
public static IQueryable<T> WithNoLock<T>(this IQueryable<T> query) where T : class
{
return query.TagWith(WithNoLockTag).AsNoTracking();
}
}SqlWithNoLockObserver تعریف میکنیم:public class SqlWithNoLockObserver : IObserver<KeyValuePair<string, object>>
{
private const string WithNoLockTagCommand = $"-- {QueryableExtensions.WithNoLockTag}";
private const string WithNoLockReplacement = "$1 $2 $3 WITH (NOLOCK)";
private static readonly Regex WithNoLockRegex = new(
@"\b(FROM|JOIN)\s+(\[?[a-zA-Z0-9_.]+\]?)\s+(AS\s+\[?[a-zA-Z0-9_]+\])",
RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.Compiled);
public void OnNext(KeyValuePair<string, object> value)
{
if (value.Key != RelationalEventId.CommandExecuting.Name) return;
var commandEventData = (CommandEventData)value.Value;
if (commandEventData.ExecuteMethod == DbCommandMethod.ExecuteNonQuery ||
!commandEventData.Command.CommandText.StartsWith(WithNoLockTagCommand)) return;
commandEventData.Command.CommandText = WithNoLockRegex.Replace(
commandEventData.Command.CommandText, WithNoLockReplacement);
}
}EfGlobalListener را برای گوش دادن به رویدادهای EF Core و فعالسازی SqlWithNoLockObserver ایجاد میکنیم:public sealed class EfGlobalListener : IObserver<DiagnosticListener>, IDisposable
{
private static readonly Lazy<EfGlobalListener> Instance = new(() => new EfGlobalListener());
private IDisposable _withNoLockSubscription;
private IDisposable _efGlobalSubscription;
private readonly SqlWithNoLockObserver _sqlWithNoLockObserver = new();
public void Subscribe()
{
if (_efGlobalSubscription != null) return;
_efGlobalSubscription = DiagnosticListener.AllListeners.Subscribe(this);
}
public void OnNext(DiagnosticListener value)
{
if (value.Name == DbLoggerCategory.Name)
{
_withNoLockSubscription = value.Subscribe(_sqlWithNoLockObserver);
}
}
}Program.cs فعال میکنیم:EfGlobalListener.Start();
AsNoTracking باعث میشود که اشیاء ردیابی نشوند، که میتواند عملکرد را در سناریوهای فقط خواندنی بهبود دهد.TransactionScope یا DbCommandInterceptor، توسعهدهندگان میتوانند این قابلیت را بهصورت انعطافپذیر پیادهسازی کنند. بااینحال، درک ریسکهای مرتبط، مانند خواندن دادههای کثیف، و ارزیابی دقیق نیازهای پروژه ضروری است.