【SQL】MySQL 的乐观锁和悲观锁

MySQL乐观锁与悲观锁详解

在MySQL中,乐观锁和悲观锁是两种常见的并发控制机制,用于确保在多事务并发环境下数据的一致性。下面是对这两种锁的详细说明。

悲观锁 (Pessimistic Locking)

悲观锁假定数据在一段时间内会被多个事务同时修改,因此在访问数据时会直接加锁,以防止其他事务对数据进行修改。悲观锁通常依赖于数据库的锁机制来实现。

实现方式

在MySQL中,可以使用 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE 来实现悲观锁。

  • FOR UPDATE:对选中的行加排他锁(X锁),其他事务不能读取和修改。
  • LOCK IN SHARE MODE:对选中的行加共享锁(S锁),其他事务可以读取但不能修改。
示例

假设有一个 employees 表:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    salary DECIMAL(10, 2)
) ENGINE=InnoDB;

事务1:

BEGIN;
SELECT * FROM employees WHERE id = 1 FOR UPDATE;

此时,事务1对 id = 1 的记录加了排他锁 (X锁)。

事务2:

BEGIN;
UPDATE employees SET salary = 6000 WHERE id = 1;

此时,事务2会被阻塞,直到事务1提交或回滚。

乐观锁 (Optimistic Locking)

乐观锁假定数据在大部分时间内不会发生冲突,因此在读取数据时不加锁,而是在提交更新时检查数据是否被其他事务修改。如果数据被修改,则回滚事务并重新尝试。

实现方式

乐观锁通常通过在表中添加一个版本号字段来实现,每次更新数据时检查版本号是否变化。

示例

假设有一个 employees 表,包含一个 version 字段:

CREATE TABLE employees (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    salary DECIMAL(10, 2),
    version INT
) ENGINE=InnoDB;

读取数据时记下版本号:

SELECT id, name, salary, version FROM employees WHERE id = 1;

更新数据时检查版本号:

UPDATE employees
SET salary = 6000, version = version + 1
WHERE id = 1 AND version = 1;

如果版本号匹配,则更新成功;如果版本号不匹配,则更新失败,需要重新读取数据并重试。

Java 代码示例

悲观锁
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class PessimisticLockExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/yourdatabase";
        String user = "yourusername";
        String password = "yourpassword";

        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            conn.setAutoCommit(false);

            String selectQuery = "SELECT * FROM employees WHERE id = ? FOR UPDATE";
            try (PreparedStatement selectStmt = conn.prepareStatement(selectQuery)) {
                selectStmt.setInt(1, 1);
                try (ResultSet rs = selectStmt.executeQuery()) {
                    if (rs.next()) {
                        System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name"));
                    }
                }
            }

            String updateQuery = "UPDATE employees SET salary = ? WHERE id = ?";
            try (PreparedStatement updateStmt = conn.prepareStatement(updateQuery)) {
                updateStmt.setBigDecimal(1, new BigDecimal("6000.00"));
                updateStmt.setInt(2, 1);
                updateStmt.executeUpdate();
            }

            conn.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
乐观锁
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class OptimisticLockExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/yourdatabase";
        String user = "yourusername";
        String password = "yourpassword";

        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            conn.setAutoCommit(false);

            String selectQuery = "SELECT id, name, salary, version FROM employees WHERE id = ?";
            int id = 1;
            int currentVersion = -1;

            try (PreparedStatement selectStmt = conn.prepareStatement(selectQuery)) {
                selectStmt.setInt(1, id);
                try (ResultSet rs = selectStmt.executeQuery()) {
                    if (rs.next()) {
                        currentVersion = rs.getInt("version");
                        System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name") + ", Version: " + currentVersion);
                    }
                }
            }

            String updateQuery = "UPDATE employees SET salary = ?, version = version + 1 WHERE id = ? AND version = ?";
            try (PreparedStatement updateStmt = conn.prepareStatement(updateQuery)) {
                updateStmt.setBigDecimal(1, new BigDecimal("6000.00"));
                updateStmt.setInt(2, id);
                updateStmt.setInt(3, currentVersion);

                int rowsAffected = updateStmt.executeUpdate();
                if (rowsAffected == 0) {
                    System.out.println("Update failed due to concurrent modification.");
                } else {
                    System.out.println("Update successful.");
                }
            }

            conn.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

这些代码示例展示了如何在Java中使用JDBC实现悲观锁和乐观锁。悲观锁通过 FOR UPDATE 语句实现,而乐观锁则依赖于版本号的检查。

<think>好的,用户想了解MySQL中的乐观锁悲观锁的区别使用场景。我之前处理过类似的问题,记得有三个引用资料,需要先回顾一下这些内容。首先,引用1提到乐观锁悲观锁的核心选择标准,包括冲突频率、响应要求数据敏感度。还有口诀,比如悲观锁像霸道总裁,先占坑再办事,适合高冲突;乐观锁像优雅绅士,先办事再确认,适合低冲突。这应该帮助用户记忆两者的特点。 引用2主要讲悲观锁适合写操作多的场景,因为排它性强,但会长时间限制访问,影响并发性能。而引用3补充了响应速度、冲突频率重试代价这三个判断因素,比如高响应需求用乐观锁,高冲突用悲观锁,重试代价大用悲观锁。现在需要整理两者的区别使用场景。区别方面,应该包括实现机制(数据库vs版本号/时间戳)、加时机(操作前 vs提交时)、性能影响(并发低 vs高)适用场景。使用场景部分需要分悲观乐观,结合引用中的例子,比如金融数据用悲观,统计用乐观;高冲突用悲观,低冲突用乐观。 还要注意用户可能的应用场景,比如银行业务,这需要强调悲观锁的重要性。同时,用户可能关心如何选择,所以需要总结口诀核心标准,方便记忆。 需要确保回答结构清晰,分点说明,并自然融入引用内容。可能还要生成相关问题,比如实现方式、死处理、性能优化等,帮助用户深入理解。</think>### MySQL 乐观锁悲观锁详解 #### 一、核心概念对比 1. **悲观锁** - **实现机制**:通过数据库(如`SELECT ... FOR UPDATE`)直接定数据行,阻止其他事务操作[^2]。 - **加时机**:在事务开始时加,操作结束后释放。 - **适用场景**:高并发写操作、金融交易(如银行转账)等需要严格保证数据一致性的场景[^1][^3]。 - **示例**: ```sql BEGIN; SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- 加悲观锁 UPDATE accounts SET balance = balance - 100 WHERE id = 1; COMMIT; ``` 2. **乐观锁** - **实现机制**:通过版本号(`version`)或时间戳(`timestamp`)实现校验,仅在提交时检查数据是否被修改[^1][^3]。 - **加时机**:无需显式加,提交时通过`WHERE`条件校验。 - **适用场景**:低冲突读多写少场景(如统计报表更新、商品库存秒杀)[^2][^3]。 - **示例**: ```sql UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 100 AND version = 5; -- 基于版本号的乐观锁 ``` #### 二、核心区别总结 | **维度** | **悲观锁** | **乐观锁** | |----------------|-----------------------------------|-----------------------------------| | **实现方式** | 数据库内置机制 | 版本号/CAS机制 | | **并发性能** | 低(长时间竞争) | 高(无等待) | | **冲突处理** | 直接阻止并发操作 | 提交失败后重试或回滚 | | **适用场景** | 高冲突写操作(如转账) | 低冲突读写操作(如库存扣减) | #### 三、选择标准与口诀 1. **核心选择标准** - **冲突频率**:高冲突用悲观锁(如秒杀系统),低冲突用乐观锁(如评论计数)[^1][^3]。 - **响应要求**:实时性高用悲观锁(如支付系统),允许重试用乐观锁(如数据同步)[^3]。 - **数据敏感度**:金融数据用悲观锁(防篡改),统计数据用乐观锁(允许容错)[^1]。 2. **记忆口诀** > "悲观锁是霸道总裁,先占坑再办事; > 乐观锁是优雅绅士,先办事再确认" [^1]。 #### 四、典型应用场景 1. **悲观锁场景** - 银行转账:事务中定账户余额,防止并发操作导致超额扣款[^1]。 - 订单库存预留:通过`FOR UPDATE`定库存,确保唯一性[^2]。 2. **乐观锁场景** - 商品库存秒杀:通过版本号控制库存扣减,避免超卖[^3]。 - 多端数据同步:提交时校验版本号,解决数据冲突[^1]。 #### 五、性能优化建议 1. 悲观锁优化: ```sql SET GLOBAL innodb_lock_wait_timeout = 30; -- 减少等待时间[^1] ``` 2. 乐观锁优化: - 采用指数退避算法(Exponential Backoff)控制重试次数[^1]。 - 避免长事务,减少版本号冲突概率。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值