乐观锁和悲观锁的区别? 写多读少的情况, 应该使用乐观锁还是悲观锁?

乐观锁和悲观锁是两种并发控制机制,它们在处理并发场景时采取不同的策略来防止数据竞争和保持数据一致性。

悲观锁(Pessimistic Lock):

  • 悲观锁假定会发生并发冲突,所以在访问数据时,会立即获取并锁定资源(例如数据库记录),直到事务结束才释放锁。在此期间,其他事务试图访问相同的资源时会被阻塞,必须等到锁被释放才能进行操作。

乐观锁(Optimistic Lock):

  • 乐观锁假设并发冲突不太可能发生,因此在读取数据时不加锁,但在更新数据时,会先验证数据自上次读取以来是否被其他事务修改过。常见的乐观锁实现方式是使用版本号或时间戳,更新数据时检查版本号是否变化,如果变化则说明数据已被修改,此时更新失败,需要重新读取并尝试更新。

写多读少的情况应该使用哪种锁:

  • 在写多读少的情况下,如果并发写入非常频繁且冲突可能性较大,使用悲观锁可能是更好的选择,因为它在一开始就阻止了并发写入,减少了因并发带来的冲突和重试次数。
  • 如果虽然写入较多,但实际冲突概率不大,或者系统对并发性能要求较高,可以考虑使用乐观锁。乐观锁在大多数情况下不阻塞其他事务,只有在更新时才会检查并发冲突,从而提升并发性能。然而,如果并发冲突确实很多,乐观锁会导致大量更新失败和重试,反而可能降低性能。

选择何种锁机制需要依据具体的并发程度、冲突概率、性能要求等因素综合决定。在写多读少且并发冲突严重的场景下,悲观锁可能更具优势,因为它在源头上避免了冲突的发生。然而在实践中,应当通过压测和性能分析来决定最适合的并发控制策略。

分布式场景下, 如何实现乐观锁?

在分布式场景下实现乐观锁,通常采用以下几种常见方式:

  1. 基于版本号(Version Number)

    • 在数据库表中为需要乐观锁控制的记录添加一个额外的“version”字段,每次更新时,不仅要满足原来的更新条件,还要将version字段值加1。
    • 当事务尝试更新时,先读取记录的当前版本号,然后在更新时将version字段作为条件(如version = 当前版本号 AND 其他更新条件),同时更新version字段为新值。
    • 如果更新返回影响的行数为0,则表示在事务执行期间,有其他事务已经修改了该记录,这时可以抛出并发异常,提示用户事务冲突并需要重试。

    示例(伪SQL):

    BEGIN TRANSACTION;
    SELECT version FROM table WHERE id = ? FOR UPDATE;
    -- 根据查询结果计算新的数据值和version值
    UPDATE table SET column = ?, version = version + 1 WHERE id = ? AND version = ?;
    COMMIT;
  2. 基于时间戳(Timestamp)

    • 类似于版本号,为记录添加一个时间戳字段,每次更新时,时间戳字段都要更新为当前时间(精确到毫秒)。
    • 在更新时,同样将时间戳字段作为条件,如果时间戳已经变更为其他值,则认为有并发冲突。
  3. 基于CAS(Compare and Swap/Set)

    • 在某些分布式数据存储系统中,提供了CAS操作,如Redis的WATCHMULTI/EXEC命令组合,或者数据库系统(如MySQL InnoDB引擎)的SELECT ... FOR UPDATE NOWAIT等。
    • 使用CAS机制时,先观察目标值,然后在一个原子操作中尝试更新,只有当目标值未被其他事务修改时,更新才有效。

举例(Redis使用Lua脚本实现乐观锁):

-- Lua Script
local key = KEYS[1]
local expected_version = tonumber(ARGV[1])
local new_data = ARGV[2]
local current_version = redis.call('GET', key)

if current_version == expected_version then
    redis.call('SET', key, new_data, 'XX') -- only set if the key already exists
    redis.call('INCR', key .. ":version") -- increment version number
    return true
else
    return false -- indicate that there was a version conflict
end

在分布式场景下,实现乐观锁的核心是确保更新操作的原子性和一致性,通过引入额外的版本号或时间戳字段,并在更新时检查其值是否发生变化,来识别并处理并发冲突。在现代数据库和缓存系统中,往往有内建的支持机制来简化这一过程。

最后编辑: kuteng  文档更新时间: 2024-04-02 09:53   作者:kuteng