EF Core 多服务器连接 MySQL 数据库并发扣除用户余额解决方案
在 EF Core 中,可以使用事务来确保多个并发操作的原子性和一致性。具体来说,在扣除用户余额时,可以使用数据库的行级锁来避免并发问题。
下面是一个使用 EF Core 和 MySQL 实现的示例代码:
// 定义用户账户实体
public class UserAccount
{
public int Id { get; set; }
public decimal Balance { get; set; }
public byte[] RowVersion { get; set; }
}
// 定义 DbContext 类
public class MyDbContext : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("server=localhost;database=mydb;user=root;password=123456");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserAccount>()
.Property(x => x.RowVersion)
.IsConcurrencyToken();
}
}
// 扣除用户余额的方法
public static void DeductBalance(int userId, decimal amount)
{
using (var db = new MyDbContext())
{
var userAccount = db.UserAccounts.Find(userId);
if (userAccount == null)
{
throw new Exception('User account not found: ' + userId);
}
// 获取行级锁
var rowVersion = userAccount.RowVersion;
db.Entry(userAccount).State = EntityState.Modified;
// 扣除余额
userAccount.Balance -= amount;
if (userAccount.Balance < 0)
{
throw new Exception('Insufficient balance: ' + userAccount.Balance);
}
// 提交事务
db.SaveChanges();
}
}
在上面的代码中,通过给实体类的 RowVersion
属性添加 IsConcurrencyToken()
标注,使得 EF Core 在更新实体时,会检查该属性的值是否和数据库中的值相同,如果不同则说明该行数据已经被其他事务修改过,此时会抛出 DbUpdateConcurrencyException
异常,从而保证了并发操作的一致性。
注意,以上代码中的行级锁只是通过 EF Core 的 EntityState.Modified
功能来实现的,实际上并没有使用 MySQL 的行级锁。如果需要使用 MySQL 的行级锁,可以在 SQL 语句中使用 SELECT ... FOR UPDATE
或者 UPDATE ... WHERE ...
带上 FOR UPDATE
来获取行级锁。例如:
-- 使用 SELECT ... FOR UPDATE 获取行级锁
SELECT * FROM UserAccounts WHERE Id = @userId FOR UPDATE;
UPDATE UserAccounts SET Balance = Balance - @amount WHERE Id = @userId;
-- 使用 UPDATE ... WHERE ... FOR UPDATE 获取行级锁
UPDATE UserAccounts SET Balance = Balance - @amount WHERE Id = @userId AND Balance >= @amount FOR UPDATE;
需要注意的是,MySQL 的行级锁只能保证同一连接中的并发操作的一致性,如果不同的连接同时修改同一行数据,就无法避免并发问题了。因此,如果需要多台服务器连接同一 MySQL 数据库,还需要使用分布式锁来保证并发操作的一致性。

原文地址: https://www.cveoy.top/t/topic/oAq6 著作权归作者所有。请勿转载和采集!