在ABP框架中,我们可以通过拦截器来全局添加NOLOCK
提示到所有的SQL查询中,以避免阻塞问题。
创建一个拦截器
我们新建一个拦截器NoLockInterceptor类,继承自DbCommandInterceptor
public class NoLockInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
// 为查询添加 NOLOCK
if (command.CommandText.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
{
command.CommandText = AddNoLockHints(command.CommandText);
}
return base.ReaderExecuting(command, eventData, result);
}
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
{
// 为查询添加 NOLOCK
if (command.CommandText.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
{
command.CommandText = AddNoLockHints(command.CommandText);
}
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
private string AddNoLockHints(string sql)
{
var pattern = @"(FROM|from|JOIN|join)\s+(\[\w+\]\.?\[\w+\]|\[\w+\])(?:\s+AS\s+(\[?\w+\]?))?(?!\s+WITH\s*\(NOLOCK\))";
var regex = new Regex(pattern, RegexOptions.IgnoreCase);
return regex.Replace(sql, match =>
{
string keyword = match.Groups[1].Value; // FROM/JOIN
string tableName = match.Groups[2].Value; // 表名
string alias = match.Groups[3].Value; // 别名(如果有)
// 如果已经包含 NOLOCK,跳过
if (match.Value.Contains("WITH(NOLOCK)", StringComparison.OrdinalIgnoreCase))
return match.Value;
// 如果有别名,将 WITH(NOLOCK) 放在别名后
if (!string.IsNullOrEmpty(alias))
return $"{keyword} {tableName} AS {alias} WITH(NOLOCK)";
// 否则直接加在表名后
return $"{keyword} {tableName} WITH(NOLOCK)";
});
}
}
在此拦截器重,我们声明了一个方法AddNoLockHints,里面有一个正则表达式
(FROM|from|JOIN|join)\s+(\[\w+\]\.?\[\w+\]|\[\w+\])(?:\s+AS\s+(\[?\w+\]?))?(?!\s+WITH\s*\(NOLOCK\))
用来替换SQL中查询表语句,这个正则的意思如下
1.匹配 FROM 或 JOIN。
2.匹配表名(如 Table、[dbo].[Table])。
3.可选匹配别名(如 AS temp、AS [temp])。
4.检查是否已存在 WITH(NOLOCK),若存在则跳过匹配。
SQL 片段 | 是否匹配 | 说明 |
FROM [dbo].[Users] | 匹配 | 匹配带架构的表 |
FROM [Users] | 匹配 | 匹配简单表 |
FROM Users | 不匹配 | 不匹配无方括号表 |
JOIN [Orders] AS [o] | 匹配 | 匹配带别名的表 |
FROM Table WITH(NOLOCK) | 不匹配 | 不匹配(已有NOLOCK) |
FROM (SELECT …) AS sub | 不匹配 | 不匹配子查询 |
这个正则的核心是:
匹配 FROM/JOIN + 表名 + 可选别名。
排除已含 WITH(NOLOCK) 的情况。
支持带方括号的表名和别名(如 [dbo].[Table] AS [t])。
重写DbContext中的OnConfiguring方法
我们打开EF对应的Context类,重写OnConfiguring方法,引入NoLockInterceptor拦截器。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(new NoLockInterceptor());//在这里引入
base.OnConfiguring(optionsBuilder);
//日志输出SQL语句
#if DEBUG
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
#endif
}
这样,我们就实现用APB框架在查询的时候,会自动添加NOLOCK了

注意事项
1.使用NOLOCK提示可以减少阻塞,但可能会导致脏读等问题。确保在你的业务逻辑中这是可接受的。
2.过度使用NOLOCK可能会对性能产生负面影响,因为它增加了读取操作的并发性。在决定使用之前,请评估其对数据一致性和性能的影响。
3.在某些数据库(如SQL Server)中,使用NOLOCK时,特别是在高并发环境下,可能需要考虑额外的隔离级别或锁提示以优化性能和一致性。例如,可以使用READPAST、ROWLOCK等选项来调整锁定的行为。 例如:SELECT * FROM Users WITH (NOLOCK, READPAST)。
根据你的具体需求和使用的数据库类型,选择最合适的方法来全局添加NOLOCK提示。