# ReshowMysqltransactionrollbackException **Repository Path**: ysphello0/reshow-mysqltransactionrollback-exception ## Basic Information - **Project Name**: ReshowMysqltransactionrollbackException - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-06 - **Last Updated**: 2026-08-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MySQL锁等待超时异常复现工程 ## 项目简介 本项目用于复现MySQL事务回滚异常:`Lock wait timeout exceeded; try restarting transaction`。 通过模拟并发环境下多个线程同时尝试获取同一行数据的行锁,展示锁等待超时异常的产生场景,并对比使用 `FOR UPDATE` 和 `FOR UPDATE SKIP LOCKED` 的不同行为。 ## 技术栈 - Java 21 - Spring Boot 3.2.0 - MyBatis-Plus 3.5.5 - MySQL 8.0+ ## 前置条件 1. 安装并启动MySQL服务 2. 创建数据库用户并授权 ## 环境配置 ### 1. 创建数据库 ```sql CREATE DATABASE example_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ``` ### 2. 配置数据库连接 修改 `src/main/resources/application.yml` 文件: ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/example_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&lockTimeout=3000 username: root password: your_password ``` ### 3. 设置MySQL锁等待超时时间(可选但推荐) 在MySQL中执行以下命令,设置较小的锁等待超时时间以便更快复现异常: ```sql SET GLOBAL innodb_lock_wait_timeout = 3; SET innodb_lock_wait_timeout = 3; ``` ### 4. 初始化数据表 执行 `src/main/resources/schema.sql` 脚本创建表结构和测试数据: ```bash mysql -u root -p example_db < src/main/resources/schema.sql ``` ## 运行测试 ### 方式一:使用Maven直接运行 ```bash cd mybatislockException mvn test -Dtest=TaskLockTest#testLockWaitTimeoutException ``` ### 方式二:在IDE中运行 1. 导入项目到IntelliJ IDEA或Eclipse 2. 运行 `TaskLockTest` 测试类中的测试方法 ## 测试用例说明 ### 1. `testLockWaitTimeoutException` **目标**:复现 `Lock wait timeout exceeded` 异常 **原理**: - 创建5个线程同时尝试获取同一批任务的锁 - 使用 `SELECT ... FOR UPDATE` 语句(不跳过已锁定记录) - 当多个线程竞争同一行数据时,未获取到锁的线程会等待 - 超过锁等待超时时间后抛出异常 **预期结果**: - 部分线程成功获取锁并处理任务 - 部分线程因锁等待超时而失败,抛出异常 ### 2. `testSkipLockedBehavior` **目标**:展示 `FOR UPDATE SKIP LOCKED` 的行为 **原理**: - 使用 `SELECT ... FOR UPDATE SKIP LOCKED` 语句 - 跳过已被其他事务锁定的记录 - 不会产生锁等待超时 **预期结果**: - 所有线程都能正常执行,不会抛出锁等待超时异常 - 已被锁定的任务会被跳过 ### 3. `testHighConcurrencyLockCompetition` **目标**:测试高并发场景下的锁竞争 **原理**: - 创建10个线程同时竞争5个任务 - 模拟更复杂的并发场景 ## 关键代码说明 ### Mapper层 ```java // 使用FOR UPDATE获取锁(可能产生锁等待) @Select("SELECT * FROM task WHERE status = #{status} ORDER BY id LIMIT 1 FOR UPDATE") Task selectForUpdate(@Param("status") String status); // 使用FOR UPDATE SKIP LOCKED获取锁(跳过已锁定记录) @Select("SELECT * FROM task WHERE status = #{status} ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED") Task selectForUpdateSkipLocked(@Param("status") String status); ``` ### Service层 ```java @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public Task processTaskWithLock(Long handlerId) { // 使用FOR UPDATE获取任务锁 Task task = taskMapper.selectForUpdate("PENDING"); // ... 更新任务状态 taskMapper.updateById(task); return task; } ``` ## 异常复现要点 1. **并发线程数 > 可用任务数**:确保有多个线程竞争同一批任务 2. **设置较小的锁等待超时时间**:建议设置为3秒 3. **事务中持有锁的时间足够长**:模拟业务处理时间 4. **使用 `FOR UPDATE` 而非 `FOR UPDATE SKIP LOCKED`**:后者会跳过已锁定记录 ## 解决方案建议 当遇到 `Lock wait timeout exceeded` 异常时,可以考虑以下解决方案: ### 方案1:使用 `FOR UPDATE SKIP LOCKED` 适用于不需要严格处理所有任务的场景,跳过已被锁定的记录。 ### 方案2:增加锁等待超时时间 ```sql SET innodb_lock_wait_timeout = 60; ``` ### 方案3:优化事务逻辑 - 缩短事务持有锁的时间 - 将非必要的操作移出事务 - 使用更细粒度的锁 ### 方案4:引入重试机制 结合 Spring Retry 实现自动重试: ```java @Retryable(value = LockWaitTimeoutException.class, maxAttempts = 3) public Task processTaskWithRetry(Long handlerId) { return processTaskWithLock(handlerId); } ``` ## 项目结构 ``` mybatislockException/ ├── pom.xml # Maven配置文件 ├── README.md # 项目说明文档 └── src/ ├── main/ │ ├── java/ │ │ └── com/example/ │ │ ├── Application.java # Spring Boot启动类 │ │ ├── entity/ │ │ │ └── Task.java # 任务实体类 │ │ ├── mapper/ │ │ │ └── TaskMapper.java # MyBatis-Plus Mapper │ │ └── service/ │ │ ├── TaskService.java # 服务接口 │ │ └── impl/ │ │ └── TaskServiceImpl.java # 服务实现 │ └── resources/ │ ├── application.yml # 应用配置 │ └── schema.sql # 数据库初始化脚本 └── test/ └── java/ └── com/example/ └── TaskLockTest.java # 测试用例 ``` ## License MIT License