【数据库】乐观锁、悲观锁通俗讲解,具体举例实现,必须懂!

本文详细介绍了乐观锁和悲观锁的概念、实现方式以及适用场景,通过银行账户余额更新示例对比两者的差异,强调了根据应用需求选择合适锁机制的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


悲观锁和乐观锁是两种处理并发访问的不同策略,它们关注的是在多个线程同时访问共享资源时如何保证数据一致性的问题。

1、悲观锁:

  • 定义: 悲观锁的基本思想是,在整个数据处理过程中,将共享资源进行加锁,以防止其他线程的干扰。
  • 实现:
    通常通过数据库的行锁或者Java中的synchronized关键字来实现。在悲观锁的情境下,线程认为在执行期间其他线程可能会修改共享资源,因此在访问共享资源之前,先获取锁,确保自己是独占资源的。例如 Collections.synchronizedMap 多线程Map用到的就是悲观锁。

2、乐观锁:

  • 定义:
    乐观锁的基本思想是,在整个数据处理过程中,不对共享资源进行加锁,而是在访问时假设其他线程不会修改共享资源,只有在真正更新时才检查是否有冲突。
  • 实现: 乐观锁的实现通常依赖于数据版本控制,通常通过版本号(版本控制)或者CAS(Compare and Swap)等机制来实现。在乐观锁的情境下,线程在读取共享资源时不会加锁,而是在更新时检查是否有其他线程修改过,如果有,则进行相应处理。
    • 版本号:每条记录可以包含一个版本号字段。读取记录时记住版本号,在更新记录时检查版本号是否发生变化(比如通过WHERE子句比较版本号)。如果版本号相同,则更新数据并增加版本号;如果不同,说明数据在此期间已被其他事务修改。
    • 时间戳:使用时间戳也是一种常见的方式,操作类似于版本号。

乐观锁适用于冲突较少的环境,因为它减少了锁的开销,但在高冲突环境中可能导致大量的重试和失败。

悲观锁: 就好比一个人在使用自动取款机(ATM)时,先取号排队,然后在自己的操作过程中,不让其他人插队或者干扰,确保自己独享ATM资源。

乐观锁:
就好比一个人在自动取款机前,直接去尝试取款,但在取款的时候,会检查自己的操作是否被其他人打扰,如果没有被打扰,就顺利完成取款;如果有冲突,就需要重新尝试或者采取其他措施。

总的来说,悲观锁更加悲观地认为会有冲突,因此提前加锁以保护资源;而乐观锁更加乐观地认为冲突不会经常发生,因此先尝试操作,再在需要的时候进行冲突检测和处理。选择乐观锁还是悲观锁,取决于应用的具体需求、数据访问模式和性能考虑。乐观锁在读多写少的场景中表现较好,而悲观锁在写操作频繁的场景中更能保证数据的一致性。在实际应用中,这两种锁常常根据具体情况和业务需求灵活使用。

3、实现

为了更好地理解乐观锁和悲观锁的具体实现,我们可以使用一个简单的银行账户余额更新作为例子。我们假设有一个名为 accounts 的表,其中包括字段 account_id(账户标识)、balance(账户余额)和 version(版本号,用于乐观锁)。

3.1. 乐观锁实现

在使用乐观锁时,我们通常在表中添加一个 version 字段。每次读取记录时,version 字段也会被读取,并在后续的更新中检查这个版本号是否改变。

  • SQL 表结构

    CREATE TABLE accounts (
    	account_id INT AUTO_INCREMENT PRIMARY KEY,
    	balance DECIMAL(10, 2),
    	version INT DEFAULT 1 ); 
    
  • 更新操作
    在进行更新操作时,检查 version 字段确保数据未被修改过,然后进行更新,并将 version 字段的值增加1。

    UPDATE accounts
    SET balance = balance + 100,  -- 假设我们要增加100元
        version = version + 1
    WHERE account_id = 1 AND version = @CurrentVersion;
    

    @CurrentVersion 是从应用程序传入的,基于最初查询得到的版本号
    如果 version 不匹配,即另一个事务已经更新了记录,这个更新操作将不会改变任何行,应用程序可以据此知道更新失败,可能需要重新尝试或通知用户。

3.2. 悲观锁实现

在使用悲观锁时,可以利用数据库提供的锁机制(如行锁),确保在当前事务完成之前,其他事务不能修改被锁定的数据。

  • SQL 表结构

    -- 使用与乐观锁相同的表结构
    CREATE TABLE accounts (
        account_id INT AUTO_INCREMENT PRIMARY KEY,
        balance DECIMAL(10, 2)
    );
    
  • 更新操作
    在查询时使用 FOR UPDATE 语句来锁定数据行。这会在当前事务完成之前阻止其他事务修改这些行。

    START TRANSACTION;
    
    SELECT balance
    FROM accounts
    WHERE account_id = 1
    FOR UPDATE;  -- 锁定账户1
    
    -- 执行业务逻辑,比如增加余额
    UPDATE accounts
    SET balance = balance + 100
    WHERE account_id = 1;
    
    COMMIT;
    

    在这个例子中,SELECT ... FOR UPDATE 语句将阻止其他任何尝试更新或读取(在需要相同锁的情况下)account_id 为 1 的行的事务,直到当前事务提交或回滚。

乐观锁和悲观锁各有用武之地,具体使用哪种锁机制取决于应用场景和并发级别。乐观锁适用于写操作较少的场景,悲观锁则适用于高冲突环境,尤其是在多用户频繁写入同一数据的场合。

### 如何将程序烧录到STM32F103C8T6最小系统板 #### 准备工作 为了成功地将程序烧录到STM32F103C8T6最小系统板上,需准备以下工具和材料: - STM32F103C8T6最小系统板 - ST-Link V2 或者其他兼容的ST-Link调试器/编程器 - USB数据线用于连接计算机与ST-Link设备 - 开发环境(如Keil MDK, IAR Embedded Workbench, STM32CubeIDE) #### 烧录过程 对于STM32系列微控制器而言,通常会把Bootloader放置于`0x8000000`地址处[^1]。然而,在实际应用中,大多数情况下并不直接操作Bootloader而是利用现成的开发工具链来完成整个编译链接以及最终的目标文件生成。 当准备好上述提到的所有必要组件之后,可以按照下面的方法来进行固件上传: 1. 将ST-Link通过Micro USB接口连接至个人电脑,并确保驱动已正确安装; 2. 使用跳帽或者杜邦线将ST-Link上的SWDIO、SWCLK、GND、NRST四个信号端子分别对应接到目标板相同名称的位置上去;如果采用的是四针排母形式,则可以直接插上即可[^2]; 3. 打开所使用的集成开发环境(IDE),加载项目工程并构建得到`.hex`或`.bin`格式的目标二进制映像文件; 4. 启动配套提供的Flash Loader Demonstration软件或者其他第三方flasher应用程序,设置好通信参数后点击“Program”按钮执行刷机动作直至提示顺利完成为止。 值得注意的是,除了官方推荐的方式外还可以借助串口实现在线更新功能,不过这往往涉及到额外的设计考量比如电路布局调整以适应UART协议的要求等复杂情况因此这里不做赘述。 ```cpp // 示例:简单的LED闪烁代码片段适用于STM32F103C8Tx芯片 #include "stm32f1xx_hal.h" int main(void){ HAL_Init(); __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA,&GPIO_InitStruct); while (true){ HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5); // 切换PA5引脚电平状态 HAL_Delay(500); // 延迟半秒时间 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值