导读:本期聚焦于小伙伴创作的《什么是数据库的悲观锁和乐观锁?在MyBatis中如何实现for update与版本号方案?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《什么是数据库的悲观锁和乐观锁?在MyBatis中如何实现for update与版本号方案?》有用,将其分享出去将是对创作者最好的鼓励。

在数据库并发场景下,多个事务同时操作同一行数据时,很容易出现数据不一致的问题,比如丢失更新、脏读等。为了避免这类问题,数据库提供了多种锁机制,其中悲观锁和乐观锁是最常用的两种并发控制方案,而MyBatis作为主流的持久层框架,也支持这两种锁的落地实现。

什么是数据库的悲观锁和乐观锁?在MyBatis中如何实现for update与版本号方案?

悲观锁与乐观锁的核心原理

悲观锁

悲观锁的核心思想是“悲观地认为并发冲突一定会发生”,因此在操作数据之前,会先对数据加锁,直到事务提交才会释放锁,这样其他事务就无法同时操作这行数据。

在关系型数据库中,悲观锁通常依赖数据库的锁机制实现,比如MySQL的InnoDB引擎支持行级锁,通过SELECT ... FOR UPDATE语句可以对查询到的行加排他锁,其他事务想要对这些行执行更新、删除或者加排他锁的操作时,会被阻塞直到当前事务提交。

悲观锁的优点是能完全避免并发冲突,数据一致性有保障,但缺点是加锁会增加系统开销,而且如果锁持有时间过长,会影响系统的并发性能,还可能引发死锁问题。

乐观锁

乐观锁的核心思想是“乐观地认为并发冲突发生的概率很低”,因此操作数据时不会先加锁,而是在更新数据时判断这段时间有没有其他事务修改过这条数据。

乐观锁最常用的实现方式是版本号机制:给数据表增加一个版本号字段,每次读取数据时把版本号一起查出来,更新数据时判断当前数据库中的版本号和之前读取的版本号是否一致,如果一致就把版本号加1并更新数据,如果不一致就说明数据已经被其他事务修改过,当前更新操作失败。

乐观锁的优点是不需要加锁,并发性能高,适合读多写少的场景,缺点是如果并发冲突频繁,会导致大量更新失败,需要业务层做重试处理。

MyBatis中悲观锁for update方案落地

在MyBatis中使用for update实现悲观锁非常简单,只需要在查询语句中添加for update后缀即可,前提是使用的数据库引擎支持行级锁,比如MySQL的InnoDB。

表结构与实体类

假设我们有一个商品库存表,结构如下:

CREATE TABLE `product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_name` varchar(50) NOT NULL COMMENT '商品名称',
  `stock` int(11) NOT NULL COMMENT '库存数量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

对应的实体类:

public class Product {
    private Long id;
    private String productName;
    private Integer stock;

    // getter和setter方法省略
}

Mapper接口与XML配置

首先在Mapper接口中定义查询方法:

public interface ProductMapper {
    /**
     * 根据id查询商品并加悲观锁
     * @param id 商品id
     * @return 商品信息
     */
    Product selectByIdForUpdate(@Param("id") Long id);
}

对应的MyBatis XML映射文件中的语句:

<select id="selectByIdForUpdate" resultType="com.example.demo.entity.Product">
    SELECT id, product_name, stock
    FROM product
    WHERE id = #{id}
    FOR UPDATE
</select>

业务层使用示例

在业务层中使用悲观锁扣减库存的示例:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
public class ProductService {
    @Resource
    private ProductMapper productMapper;

    /**
     * 扣减库存(悲观锁方案)
     * @param productId 商品id
     * @param reduceCount 扣减数量
     * @return 是否扣减成功
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean reduceStockWithPessimisticLock(Long productId, Integer reduceCount) {
        // 查询商品并加悲观锁,此时其他事务无法修改该商品数据
        Product product = productMapper.selectByIdForUpdate(productId);
        if (product == null) {
            throw new RuntimeException("商品不存在");
        }
        if (product.getStock() < reduceCount) {
            throw new RuntimeException("库存不足");
        }
        // 扣减库存
        product.setStock(product.getStock() - reduceCount);
        int updateCount = productMapper.updateById(product);
        return updateCount > 0;
    }
}

对应的更新方法XML配置:

<update id="updateById" parameterType="com.example.demo.entity.Product">
    UPDATE product
    SET stock = #{stock}
    WHERE id = #{id}
</update>

需要注意的是,for update语句必须在事务中生效,否则查询完之后锁会立刻释放,达不到悲观锁的效果,因此业务方法上需要添加@Transactional注解开启事务。

MyBatis中乐观锁版本号方案落地

乐观锁的版本号方案需要先给数据表增加版本号字段,然后在更新时判断版本号是否匹配。

表结构改造与实体类更新

给商品表增加版本号字段:

ALTER TABLE product ADD COLUMN `version` int(11) NOT NULL DEFAULT 0 COMMENT '版本号';

更新后的实体类:

public class Product {
    private Long id;
    private String productName;
    private Integer stock;
    private Integer version;

    // getter和setter方法省略
}

Mapper接口与XML配置

Mapper接口定义查询和更新方法:

public interface ProductMapper {
    /**
     * 根据id查询商品(乐观锁用,不需要加锁)
     * @param id 商品id
     * @return 商品信息
     */
    Product selectById(@Param("id") Long id);

    /**
     * 乐观锁更新库存,只有版本号匹配时才更新,同时版本号加1
     * @param id 商品id
     * @param reduceCount 扣减数量
     * @param oldVersion 旧的版本号
     * @return 更新影响的行数
     */
    int updateStockWithOptimisticLock(@Param("id") Long id, @Param("reduceCount") Integer reduceCount, @Param("oldVersion") Integer oldVersion);
}

对应的XML配置:

<select id="selectById" resultType="com.example.demo.entity.Product">
    SELECT id, product_name, stock, version
    FROM product
    WHERE id = #{id}
</select>

<update id="updateStockWithOptimisticLock">
    UPDATE product
    SET stock = stock - #{reduceCount},
        version = version + 1
    WHERE id = #{id}
      AND version = #{oldVersion}
</update>

业务层使用示例

乐观锁扣减库存的业务逻辑:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
public class ProductService {
    @Resource
    private ProductMapper productMapper;

    /**
     * 扣减库存(乐观锁版本号方案)
     * @param productId 商品id
     * @param reduceCount 扣减数量
     * @return 是否扣减成功
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean reduceStockWithOptimisticLock(Long productId, Integer reduceCount) {
        // 查询商品信息,获取当前版本号
        Product product = productMapper.selectById(productId);
        if (product == null) {
            throw new RuntimeException("商品不存在");
        }
        if (product.getStock() < reduceCount) {
            throw new RuntimeException("库存不足");
        }
        // 执行乐观锁更新,返回影响的行数
        int updateCount = productMapper.updateStockWithOptimisticLock(productId, reduceCount, product.getVersion());
        if (updateCount == 0) {
            // 更新失败,说明版本号不匹配,数据被其他事务修改过
            throw new RuntimeException("并发冲突,扣减库存失败,请重试");
        }
        return true;
    }
}

如果业务中需要更高的成功率,可以在更新失败的时候加入重试逻辑,比如重试3次,每次重试前重新查询最新的商品信息和版本号再执行更新操作。

两种方案的选型建议

在实际项目中,选择悲观锁还是乐观锁需要结合业务场景:

  • 如果业务场景是写多读少,或者并发冲突概率高,比如库存扣减、账户余额变更这类操作,建议使用悲观锁,能避免大量更新失败的问题。
  • 如果业务场景是读多写少,并发冲突概率低,比如商品信息修改、用户资料更新这类操作,建议使用乐观锁,性能更高,也不会产生死锁问题。

另外需要注意,for update悲观锁如果查询条件没有命中索引,会从行锁升级为表锁,会严重影响数据库的并发性能,因此使用悲观锁时一定要确保查询条件使用了索引。

悲观锁乐观锁MyBatisfor_update版本号修改时间:2026-06-18 03:45:55

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。