MySQL 事务、锁与隔离级别的BUG并发问题
·
目录:
一、事务的 ACID 与隔离级别
事务的 ACID 老生常谈:
- Atomicity 原子性:要么全成功,要么全回滚
- Consistency 一致性:数据从一个合法状态变到另一个合法状态
- Isolation 隔离性:并发事务互相不干扰
- Durability 持久性:提交后不丢
ACID 里最复杂的是 I(隔离性),因为完全隔离(串行执行)性能差,所以 SQL 标准定义了 4 个隔离级别:

MySQL 默认是 RR,PostgreSQL 和 Oracle 默认是 RC。如果你从 PG 转 MySQL,要注意这个差异。
查看 / 修改:
SELECT @@transaction_isolation; -- 看会话级别
SELECT @@global.transaction_isolation; -- 看全局级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
二、MVCC完整实现流程
1. 写操作流程

2. 读操作流程

三、商品下单案例MVCC的使用
1.项目场景
- 用户下单时需要检查库存
- 多个用户同时下单
- 需要保证库存准确性
2.数据表的设计
CREATE TABLE product (
id INT PRIMARY KEY,
name VARCHAR(50),
stock INT,
-- 隐藏字段(InnoDB自动添加)
DB_TRX_ID BIGINT,
DB_ROLL_PTR VARCHAR(20)
) ENGINE=InnoDB;
3.代码实现
1. Service层 - 下单逻辑
@Service
public class OrderService {
@Autowired
private ProductRepository productRepository;
@Autowired
private OrderRepository orderRepository;
@Transactional
public Order createOrder(Long productId, int quantity) {
// 1. 查询商品(快照读 - MVCC)
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("商品不存在"));
// 2. 检查库存(当前读 - 加锁)
if (product.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
// 3. 扣减库存(当前读 - 加锁)
product.setStock(product.getStock() - quantity);
productRepository.save(product);
// 4. 创建订单
Order order = new Order();
order.setProductId(productId);
order.setQuantity(quantity);
order.setStatus(OrderStatus.CREATED);
return orderRepository.save(order);
}
}
2. Repository层 - 数据访问
@Repository
public class ProductRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
// 普通查询 - 使用MVCC(快照读)
public Product findById(Long id) {
String sql = "SELECT * FROM product WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{id},
(rs, rowNum) -> {
Product product = new Product();
product.setId(rs.getLong("id"));
product.setName(rs.getString("name"));
product.setStock(rs.getInt("stock"));
return product;
});
}
// 更新操作 - 当前读(加锁)
public void updateStock(Long id, int newStock) {
String sql = "UPDATE product SET stock = ? WHERE id = ?";
jdbcTemplate.update(sql, newStock, id);
}
}
3. 事务配置
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
四、MVCC在项目中的体现
场景1:库存查询(快照读)
// 用户A查询库存 - 读取历史版本
Product productA = productRepository.findById(1L);
// 返回 stock = 100
// 用户B同时查询库存 - 也读取历史版本
Product productB = productRepository.findById(1L);
// 返回 stock = 100(同一事务内的快照)
场景2:库存更新(当前读)
// 用户A下单 - 当前读,加X锁
@Transactional
public void orderA() {
Product product = productRepository.findById(1L); // 读取最新版本
product.setStock(99);
productRepository.save(product); // 加锁更新
}
// 用户B下单 - 等待锁释放
@Transactional
public void orderB() {
// 等待用户A的事务完成
Product product = productRepository.findById(1L);
product.setStock(98);
productRepository.save(product);
}
最终测试:并发测试代码
@SpringBootTest
public class ConcurrencyTest {
@Autowired
private OrderService orderService;
@Test
public void testConcurrency() throws InterruptedException {
// 模拟100个用户同时下单
ExecutorService executor = Executors.newFixedThreadPool(100);
CountDownLatch latch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
try {
orderService.createOrder(1L, 1);
} catch (Exception e) {
System.out.println("下单失败: " + e.getMessage());
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
// 验证最终库存
Product product = productRepository.findById(1L);
assertEquals(0, product.getStock());
}
}
更多推荐



所有评论(0)