乐观锁和悲观锁

最近被问到这个东西,所以查找一下资料然后自己归纳总结一下这两个东西。

乐观锁

概念:乐观锁假设并发冲突不常发生,允许多个事务同时访问数据,但在提交时检查数据是否被修改过(个人认为就是最后检查一下是否某个值比如id或版本,来判定是否有人修改了资料,有就不允许其他事务操作了
一般通过某个标识来判定资料是否被异动

  1. 版本号机制
// 实体类
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int Version { get; set; } // 版本号字段
}


// 更新时检查版本
UPDATE Products 
SET Name = @NewName, Price = @NewPrice, Version = Version + 1 
WHERE Id = @Id AND Version = @OriginalVersion
  1. 时间戳机制
public class Order
{
    public int Id { get; set; }
    public DateTime Timestamp { get; set; } // 时间戳字段
}

// SQL
UPDATE Orders 
SET Status = @NewStatus, Timestamp = GETDATE() 
WHERE Id = @Id AND Timestamp = @OriginalTimestamp

事例:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    [Timestamp] // 使用时间戳作为乐观锁
    public byte[] RowVersion { get; set; }
}

// 更新时处理并发冲突
try
{
    var product = context.Products.Find(id);
    product.Name = "New Name";
    context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
    // 处理冲突
    var entry = ex.Entries[0];
    var databaseValues = entry.GetDatabaseValues();
    // 决定如何处理(重试/合并/取消)
}

最后UPDATE时判定是否通过刚开始查询出来的标识带入到UPDATE条件看是否会有影响行,来判定资料是有有给人异动( 更新失败后考虑是否要加入重试机制或人员手动触发按钮,捞取最新资料再次进行资料异动)

悲观锁

概念:悲观锁假设并发冲突经常发生,在访问数据前先加锁,阻止其他事务访问(个人认为就是最开始就认定他会被改,只允许一个人改,加了个锁后,其他人就不能直接访问,有点像同步锁的概念

实现方式

  1. 数据库行锁(注意加了行锁之后,需要事务commit,rockback才会释放)
-- SQL Server
BEGIN TRANSACTION
SELECT * FROM Products WITH (UPDLOCK) WHERE Id = 1
-- 执行更新操作
UPDATE Products SET Stock = Stock - 1 WHERE Id = 1
COMMIT TRANSACTION
  1. C# 代码实现
// 使用锁对象
private static readonly object _lockObj = new object();

public void UpdateProduct(int productId)
{
    lock (_lockObj)
    {
        // 执行更新操作
    }
}

事例

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    using (var transaction = connection.BeginTransaction())
    {
        try
        {
            // 获取悲观锁
            var cmd = connection.CreateCommand();
            cmd.Transaction = transaction;
            cmd.CommandText = "SELECT * FROM Accounts WITH (UPDLOCK) WHERE Id = @id";
            cmd.Parameters.AddWithValue("@id", accountId);
            var reader = cmd.ExecuteReader();
            
            // 执行更新操作
            if (reader.Read())
            {
                var updateCmd = connection.CreateCommand();
                updateCmd.Transaction = transaction;
                updateCmd.CommandText = "UPDATE Accounts SET Balance = Balance - @amount WHERE Id = @id";
                updateCmd.Parameters.AddWithValue("@id", accountId);
                updateCmd.Parameters.AddWithValue("@amount", amount);
                updateCmd.ExecuteNonQuery();
            }
            
            transaction.Commit();
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
    }
}

悲观锁保证了事务的强一致性,但是有可能会导致死锁的现象,需要注意

在实际应用中,可以结合两种锁策略:

// 先尝试乐观锁
try
{
    OptimisticUpdate();
}
catch (ConcurrencyException)
{
    // 如果乐观锁失败,使用悲观锁重试
    PessimisticUpdate();
}

总结:

乐观锁与悲观锁核心区别对比表(C#实现示例)

对比维度悲观锁 (Pessimistic Lock)乐观锁 (Optimistic Lock)
核心思想认为数据并发冲突概率高,先加锁再操作,确保独占性认为数据并发冲突概率低,先操作后校验,冲突时重试
实现机制依赖锁机制强制独占访问不依赖锁,通过版本号机制CAS算法实现
锁粒度粒度较粗,锁开销较大无实际锁,仅通过版本标识控制,开销极小
并发性能并发量高时,锁竞争激烈,容易导致线程阻塞,性能下降并发量高时无锁竞争,线程无需阻塞,性能更优
阻塞情况会阻塞其他线程的读写操作不会阻塞线程,冲突时仅通过重试机制处理,无阻塞等待
适用场景写操作频繁、并发冲突概率高的场景(如库存扣减、订单支付)读操作频繁、并发冲突概率低的场景(如数据查询、信息展示)
数据一致性强一致性,全程锁定数据,避免脏读、不可重复读、幻读最终一致性,仅在提交时校验冲突,可能出现中间态不一致
实现示例- C#:lock关键字
- Monitor.Enter/Exit
- Mutex互斥体
- 数据库:SELECT ... FOR UPDATE(排他锁)
- C#:Interlocked类(基于CAS算法)
- 自定义版本号机制(如实体类添加Version属性,更新时校验)
- Entity Framework的[Timestamp]特性
冲突处理方式冲突时阻塞等待,直到锁释放后重新竞争冲突时直接放弃操作或触发重试(需业务层处理重试逻辑,避免死循环)
死锁风险存在死锁风险(如多个线程交叉持有锁并等待对方释放)无死锁风险(无实际锁竞争,仅依赖版本校验)

补充说明:

  • 悲观锁C#示例:使用lock关键字确保代码块原子性

    private readonly object _lockObj = new object();
    
    public void UpdateData()
    {
        lock (_lockObj)  // 加锁
        {
            // 执行数据更新操作
        }
    }
    
  • 乐观锁C#示例:使用Interlocked类(CAS操作)

    private int _counter = 0;
    
    public void IncrementCounter()
    {
        int original, newValue;
        do
        {
            original = _counter;
            newValue = original + 1;
            // 比较并交换:如果当前值等于original,则更新为newValue
        } while (Interlocked.CompareExchange(ref _counter, newValue, original) != original);
    }
    ```   |
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值