Dapper 事务,读取时锁定行
Dapper transaction, lock row on read
我需要 dapper 的读锁,因为我有一个特定的情况,我需要从数据库中读取行并锁定它,然后执行一些其他操作。
这就是我所说的小巧玲珑:
protected async Task<List<T>> LoadData<T, U>(string sql, U parameters)
{
var rows = await getConnection().QueryAsync<T>(sql, parameters, commandType: CommandType.StoredProcedure, transaction: getTransaction());
return rows.ToList();
}
但是没有行被锁定。
有趣的是,保存数据的交易工作得很好。
protected async Task<int> SaveData<U>(string sql, CommandType commandType, U parameters)
{
return await getConnection().ExecuteAsync(sql, parameters, commandType: commandType, transaction: getTransaction());
}
在调用 dapper 之前打开连接并开始事务...像这样:
if (connection.State != ConnectionState.Open)
connection.Open();
transaction = connection.BeginTransaction(IsolationLevel.Serializable);
SQL 查询是一个简单的 select 语句,如下所示:
SELECT * FROM [dbo].[Tab01] WHERE [Id] = @Id
在 SQL 服务器中,SERIALIZABLE 事务不会对 SELECT 查询使用排他锁或限制锁。相反,在与您的 SELECT 查询相对应的事务期间,会获取并持有共享锁。所以如果你 运行
SELECT * FROM [dbo].[Tab01] WHERE [Id] = @Id
您的事务将在 Tab01 的 Id 索引上接收共享 (S) 键范围锁,覆盖 @Id 的值。这将防止任何其他会话更改该行,或插入具有该 ID 的行。
如果两个 SERIALIZABLE 事务都运行
SELECT * FROM [dbo].[Tab01] WHERE [Id] = @Id
对于相同的 ID,然后两者都尝试插入具有该值的行,或更新该行,一个将因死锁而失败。所以 SERIALIZABLE 确实 防止会话覆盖彼此的数据,它使用死锁错误来做到这一点,这可能很不方便。
SERIALIZABLE 保证提交的结果与会话已序列化(一次执行一个)相同,但它实际上并不执行该序列化。它乐观地允许并发事务读取相同的数据,但多个会话试图更新已读取的数据,这将导致死锁。
在 SQL 中,服务器要对读取设置限制性锁需要锁提示。通常你使用:
SELECT * FROM [dbo].[Tab01] with (updlock,serializable) WHERE [Id] = @Id
强制读取使用限制性更强的更新 (U) 锁,并使用 SERIALIZABLE 样式的键范围锁定,以防当前没有具有该 ID 的行。
长时间持有锁是一个非常非常糟糕的主意。因为您无法控制锁定的粒度和长度,所以您最终可能会锁定一半的数据库。
你应该做的是两个选项之一
要么在单个批次中使用事务执行您的“操作”
或者使用其他机制锁定该行。对于第二个选项,您可能有一个 LockedBy
列,或者可能使用 sp_getapplock
.
您的代码还有一个问题:
所有迹象表明您正在其他地方创建和缓存连接和事务对象,而不是使用 using
处理。
您的代码应该如下所示:
protected Task DoStuff()
{
using(var connection = new SqlConnection(getConnectionString())
{
LoadData(conn, ....)
}
}
protected async Task<List<T>> LoadData<T, U>(SqlConnection conn, string sql, U parameters)
{
using(var tran = conn.BeginTransaction())
{
var rows = await conn.QueryAsync<T>(sql, parameters, commandType: CommandType.StoredProcedure, transaction: tran);
tran.Commit();
return rows.ToList();
}
}
protected async Task<int> SaveData<U>(SqlConnection conn, string sql, CommandType commandType, U parameters)
{
using(var tran = conn.BeginTransaction())
{
var result = await conn.ExecuteAsync(sql, parameters, commandType: commandType, transaction: tran);
tran.Commit();
return result;
}
}
另见 C# Data Connections Best Practice?
我需要 dapper 的读锁,因为我有一个特定的情况,我需要从数据库中读取行并锁定它,然后执行一些其他操作。
这就是我所说的小巧玲珑:
protected async Task<List<T>> LoadData<T, U>(string sql, U parameters)
{
var rows = await getConnection().QueryAsync<T>(sql, parameters, commandType: CommandType.StoredProcedure, transaction: getTransaction());
return rows.ToList();
}
但是没有行被锁定。 有趣的是,保存数据的交易工作得很好。
protected async Task<int> SaveData<U>(string sql, CommandType commandType, U parameters)
{
return await getConnection().ExecuteAsync(sql, parameters, commandType: commandType, transaction: getTransaction());
}
在调用 dapper 之前打开连接并开始事务...像这样:
if (connection.State != ConnectionState.Open)
connection.Open();
transaction = connection.BeginTransaction(IsolationLevel.Serializable);
SQL 查询是一个简单的 select 语句,如下所示:
SELECT * FROM [dbo].[Tab01] WHERE [Id] = @Id
在 SQL 服务器中,SERIALIZABLE 事务不会对 SELECT 查询使用排他锁或限制锁。相反,在与您的 SELECT 查询相对应的事务期间,会获取并持有共享锁。所以如果你 运行
SELECT * FROM [dbo].[Tab01] WHERE [Id] = @Id
您的事务将在 Tab01 的 Id 索引上接收共享 (S) 键范围锁,覆盖 @Id 的值。这将防止任何其他会话更改该行,或插入具有该 ID 的行。
如果两个 SERIALIZABLE 事务都运行
SELECT * FROM [dbo].[Tab01] WHERE [Id] = @Id
对于相同的 ID,然后两者都尝试插入具有该值的行,或更新该行,一个将因死锁而失败。所以 SERIALIZABLE 确实 防止会话覆盖彼此的数据,它使用死锁错误来做到这一点,这可能很不方便。
SERIALIZABLE 保证提交的结果与会话已序列化(一次执行一个)相同,但它实际上并不执行该序列化。它乐观地允许并发事务读取相同的数据,但多个会话试图更新已读取的数据,这将导致死锁。
在 SQL 中,服务器要对读取设置限制性锁需要锁提示。通常你使用:
SELECT * FROM [dbo].[Tab01] with (updlock,serializable) WHERE [Id] = @Id
强制读取使用限制性更强的更新 (U) 锁,并使用 SERIALIZABLE 样式的键范围锁定,以防当前没有具有该 ID 的行。
长时间持有锁是一个非常非常糟糕的主意。因为您无法控制锁定的粒度和长度,所以您最终可能会锁定一半的数据库。
你应该做的是两个选项之一
要么在单个批次中使用事务执行您的“操作”
或者使用其他机制锁定该行。对于第二个选项,您可能有一个
LockedBy
列,或者可能使用sp_getapplock
.
您的代码还有一个问题:
所有迹象表明您正在其他地方创建和缓存连接和事务对象,而不是使用 using
处理。
您的代码应该如下所示:
protected Task DoStuff()
{
using(var connection = new SqlConnection(getConnectionString())
{
LoadData(conn, ....)
}
}
protected async Task<List<T>> LoadData<T, U>(SqlConnection conn, string sql, U parameters)
{
using(var tran = conn.BeginTransaction())
{
var rows = await conn.QueryAsync<T>(sql, parameters, commandType: CommandType.StoredProcedure, transaction: tran);
tran.Commit();
return rows.ToList();
}
}
protected async Task<int> SaveData<U>(SqlConnection conn, string sql, CommandType commandType, U parameters)
{
using(var tran = conn.BeginTransaction())
{
var result = await conn.ExecuteAsync(sql, parameters, commandType: commandType, transaction: tran);
tran.Commit();
return result;
}
}
另见 C# Data Connections Best Practice?