导读:本期聚焦于小伙伴创作的《MySQL多线程更新同一行数据如何处理?利用CAS无锁编程思维可行吗》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《MySQL多线程更新同一行数据如何处理?利用CAS无锁编程思维可行吗》有用,将其分享出去将是对创作者最好的鼓励。

在并发业务场景下,多个线程同时操作MySQL同一行数据是极为常见的需求,比如电商系统中多个用户同时抢购同一件商品,库存字段的更新就涉及多线程操作同一行的问题,处理不当会导致库存超扣、数据更新丢失等异常。

MySQL多线程更新同一行数据如何处理?利用CAS无锁编程思维可行吗

多线程更新同一行数据的常见问题

当多个线程同时执行更新同一行数据的操作时,如果不做并发控制,会出现两类典型问题。第一类是更新丢失,比如线程A读取到某行数据的字段值为10,线程B也读取到该值为10,线程A将其更新为8,线程B随后将其更新为9,最终结果为9,线程A的更新被覆盖。第二类是数据不一致,比如更新涉及多个字段,不同线程交叉更新不同字段,最终得到不符合业务预期的数据组合。

CAS无锁编程的核心思想

CAS即Compare And Swap,是无锁编程中常用的一种并发控制思路,核心逻辑可以概括为三个操作数:内存地址V、旧预期值A、新值B。当且仅当内存地址V的当前值等于旧预期值A时,才将V的值修改为B,否则不做任何操作,整个过程是原子性的。在Java等编程语言中,很多原子类的实现都基于CAS思路,不需要加锁就能实现并发安全的数据更新。

MySQL中借鉴CAS思路的实现方式

MySQL本身没有原生的CAS指令,但是可以通过SQL语句的组合来模拟CAS的逻辑,核心是利用更新语句的条件判断实现比较和交换的原子性。MySQL的UPDATE语句本身是原子操作,我们可以在UPDATE语句的WHERE条件中加上旧值的判断,只有当当前行的值和预期旧值一致时才执行更新。

基于UPDATE条件判断的CAS式实现

假设我们有一张商品表,结构如下:

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

现在多个线程要同时扣减id为1的商品库存,每个线程扣减1件,传统的不安全更新语句如下:

-- 先查询当前库存
SELECT stock FROM product WHERE id = 1;
-- 假设查到库存为10,再执行更新
UPDATE product SET stock = 9 WHERE id = 1;

这种写法查询和更新是两个独立的操作,不是原子的,就会出现前面提到的更新丢失问题。我们可以借鉴CAS的思路,把查询和更新的逻辑合并到一个原子操作中:

-- 假设线程读取到的旧库存是10,要扣减1件,新库存是9
UPDATE product SET stock = 9 WHERE id = 1 AND stock = 10;

这条UPDATE语句会先判断id为1的行的stock值是否等于10,如果等于才更新为9,否则不执行更新。我们可以通过判断语句的受影响行数来确定是否更新成功,如果受影响行数为1,说明更新成功;如果为0,说明当前行的stock已经不是10了,更新失败,需要重试。

重试机制的实现

CAS操作失败后通常需要重试,我们可以结合业务代码实现重试逻辑,以下是Java中使用JDBC实现的示例:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class ProductStockUpdater {
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    // 最大重试次数
    private static final int MAX_RETRY = 3;

    public boolean deductStock(int productId, int deductNum) throws SQLException {
        int retryCount = 0;
        while (retryCount < MAX_RETRY) {
            Connection conn = null;
            PreparedStatement selectPs = null;
            PreparedStatement updatePs = null;
            ResultSet rs = null;
            try {
                conn = DriverManager.getConnection(URL, USER, PASSWORD);
                // 1. 查询当前库存作为旧预期值
                String selectSql = "SELECT stock FROM product WHERE id = ?";
                selectPs = conn.prepareStatement(selectSql);
                selectPs.setInt(1, productId);
                rs = selectPs.executeQuery();
                if (!rs.next()) {
                    // 商品不存在
                    return false;
                }
                int oldStock = rs.getInt("stock");
                // 库存不足
                if (oldStock < deductNum) {
                    return false;
                }
                int newStock = oldStock - deductNum;
                // 2. 执行CAS式更新
                String updateSql = "UPDATE product SET stock = ? WHERE id = ? AND stock = ?";
                updatePs = conn.prepareStatement(updateSql);
                updatePs.setInt(1, newStock);
                updatePs.setInt(2, productId);
                updatePs.setInt(3, oldStock);
                int affectedRows = updatePs.executeUpdate();
                if (affectedRows > 0) {
                    // 更新成功
                    return true;
                }
                // 更新失败,重试
                retryCount++;
                // 重试前短暂休眠,避免频繁重试
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            } finally {
                // 关闭资源
                if (rs != null) {
                    rs.close();
                }
                if (selectPs != null) {
                    selectPs.close();
                }
                if (updatePs != null) {
                    updatePs.close();
                }
                if (conn != null) {
                    conn.close();
                }
            }
        }
        // 重试多次仍失败
        return false;
    }
}

不同方案的对比与适用场景

除了这种CAS式的实现,MySQL中处理多线程更新同一行数据还有其他常见方案,我们可以通过表格对比不同方案的特点:

方案实现思路优点缺点适用场景
行锁(FOR UPDATE)查询时加排他行锁,锁定该行直到事务提交实现简单,并发安全有保障锁持有时间长会影响并发性能,可能导致死锁更新逻辑复杂,需要同时更新多个字段的场景
乐观锁(版本号)表中加版本号字段,更新时判断版本号是否匹配读多写少场景下性能较好,无死锁风险需要额外维护版本号字段,写冲突多时重试次数多读多写少,并发冲突不频繁的场景
CAS式更新利用UPDATE条件判断实现比较交换,配合重试无需额外字段,实现轻量,原子性由数据库保证写冲突多时重试开销大,仅适用于单字段或简单更新逻辑简单的单字段更新,并发冲突频率中等的场景

注意事项

  • 使用CAS式更新时,要确保UPDATE语句的条件能够准确匹配旧预期值,避免条件遗漏导致更新异常。
  • 重试次数需要设置上限,避免无限重试导致线程阻塞,重试间隔可以根据业务场景调整。
  • 如果更新涉及多个字段或者复杂的业务逻辑,CAS式更新可能不够灵活,此时可以考虑行锁或者乐观锁方案。
  • MySQL的UPDATE语句在InnoDB引擎下默认是行级锁,只要WHERE条件命中索引,就不会锁表,性能有保障。

总的来说,MySQL中处理多线程更新同一行数据时,借鉴CAS无锁编程的思维是可行的,通过UPDATE语句的条件判断配合重试机制,就能实现轻量级的并发控制,开发者可以根据实际的业务场景选择合适的方案。

MySQL多线程更新CAS无锁编程行锁乐观锁修改时间:2026-07-10 10:06:42

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