Skip to content

实现乐观锁的方式有什么?在数据库层面如何实现?

乐观锁(Optimistic Locking)是一种并发控制策略,它假设多个事务在处理时不会彼此互相影响,因此在数据处理过程中不加锁,而是在提交更新时才检测是否存在冲突。如果检测到冲突,则当前操作会失败并通常需要重试,否则执行更新。这种策略适用于读多写少的场景,能够提高系统的并发性能,因为它减少了对数据库资源的竞争和锁的开销。

乐观锁的实现方式主要有以下几种:

1. 使用数据版本(Version)记录机制

这是乐观锁最常用的一种实现方式。

  • 实现原理:

    1. 在数据库表中为数据增加一个数字类型的“version”字段,表示数据的版本号,或者说是数据被修改的次数。
    2. 当读取数据时,将version字段的值一同读出。
    3. 当需要更新数据时,将客户端或应用程序中保存的旧版本号与数据库中当前数据的版本号进行比对。
    4. 如果两者一致,则执行更新操作,并将数据库中的version值加一。
    5. 如果两者不一致,说明数据已经被其他事务修改过,当前更新操作失败(通常会抛出异常或返回错误信息,让用户决定重试或其他处理)。
  • 数据库层面实现: 通常在UPDATE语句中加入version字段作为WHERE条件。

    sql UPDATE your_table SET column1 = new_value1, column2 = new_value2, version = version + 1 -- 版本号加一 WHERE id = record_id AND version = current_version; -- 比较旧版本号 如果这条SQL语句执行后影响的行数为0,则表示更新失败,发生了并发冲突。

2. 使用时间戳(Timestamp)机制

时间戳机制与版本号类似,利用数据的最后修改时间来判断是否发生冲突。

  • 实现原理:

    1. 在需要乐观锁控制的表中增加一个时间戳(timestampdatetime)字段,用于记录数据最后一次被修改的时间。
    2. 当读取数据时,将该时间戳字段的值一同读出。
    3. 当需要更新数据时,将客户端或应用程序中保存的旧时间戳与数据库中当前数据的最新时间戳进行比对。
    4. 如果两者一致,则执行更新操作,并更新时间戳字段为当前系统时间。
    5. 如果两者不一致,说明数据已被其他事务修改,当前更新操作失败。
  • 数据库层面实现:

    sql UPDATE your_table SET column1 = new_value1, column2 = new_value2, last_modified_time = NOW() -- 更新时间戳 WHERE id = record_id AND last_modified_time = old_last_modified_time; -- 比较旧时间戳 时间戳天然具有顺序递增性,因此可以作为版本号的替代。

3. Compare And Swap (CAS) 算法

CAS(Compare And Swap)是一种无锁原子操作,是乐观锁的一种实现方式。

  • 实现原理: CAS操作涉及三个值:

    • V (Value): 内存中当前变量的值。
    • A (Expected): 预期值(即你认为V应该是什么)。
    • B (New): 新值(即你想将V更新为什么)。
    • 当且仅当V与A相等时,CAS会原子地将V更新为B。否则,V保持不变,操作失败。
  • 在数据库层面: 虽然CAS本身是CPU指令级别的原子操作,但在数据库层面,上述的版本号和时间戳机制都可以被视为遵循CAS思想。 UPDATE ... WHERE version = old_version 这条SQL语句在执行时,数据库会先检查version字段的值是否等于old_version(比较),如果相等,则更新columnversion字段(交换),这个过程在数据库内部通常是原子性的。

数据库层面的其他相关概念

  • 乐观锁并非数据库自带,通常需自行实现。 乐观锁的实现逻辑通常是在应用程序层面完成的,通过利用数据库的字段(如版本号或时间戳)和原子更新操作来达成。
  • 数据库存储过程: 可以在数据库存储过程中实现乐观锁策略,将冲突检测和数据更新逻辑封装起来,对外只开放基于存储过程的数据更新途径,而不是直接对外公开数据库表。
  • 多版本并发控制(MVCC):
    • MVCC(Multi-Version Concurrency Control)是一种数据库并发控制技术,它通过为数据记录保存多个版本来提高数据库的并发性能。
    • MVCC 允许事务读取数据的一个历史快照(称为快照读),而不是加锁阻塞其他写操作,从而解决了读写冲突。
    • MVCC 与乐观锁在一定程度上都体现了“乐观”的思想,即不加锁地允许读操作。然而,MVCC 主要关注读写冲突,允许读事务在不阻塞写事务的情况下看到一致的数据版本;而传统的乐观锁(如版本号机制)则主要用于解决写写冲突,确保在更新数据时检测到其他并发的修改。
    • MySQL InnoDB 引擎就实现了 MVCC。快照读就是通过 MVCC 来控制的,不加锁。