使用mysql悲观锁解决商品超卖问题

本文介绍了在高并发场景下,如何利用MySQL悲观锁解决商品超卖问题。通过设置事务,确保在查询商品信息后锁定数据,避免并发修改导致库存异常。并展示了未加锁与加锁后的测试对比,证明了悲观锁的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

悲观锁介绍(百科):

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

使用场景举例:以MySQL InnoDB为例

商品goods表,假设商品的id为1,购买数量为1,status为1表示上架中,2表示下架。现在用户购买此商品,在不是高并发的情况下处理逻辑是:

  1. 查找此商品的信息;
  2. 检查商品库存是否大于购买数量;
  3. 修改商品库存和销量;

上面这种场景在高并发访问的情况下很可能会出现问题。如果商品库存是100个,高并发的情况下可能会有1000个同时访问,在到达第2步的时候,都会检测通过。这样会出现商品库存是-900个的情况。显然着不满足需求!!!

商品表结构:

CREATE TABLE `goods` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `status` tinyint(1) NOT NULL DEFAULT '1',
  `total` int(11) NOT NULL DEFAULT '0',
  `sell` int(11) NOT NULL DEFAULT '100',
  `price` decimal(10,2) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `test`.`goods`(`id`, `name`, `status`, 
<think>我们正在讨论MySQL悲观锁。根据用户的问题,我们需要解释悲观锁的作用、使用方法和适用场景。结合引用内容,我们可以整理如下:1.**作用**:悲观锁用于在并发环境下确保数据操作的一致性。它假设在数据处理过程中很可能发生冲突,因此在对数据进行操作前就加锁,防止其他事务同时修改或读取该数据。2.**使用方法**:-**排他锁(X锁)**:使用`SELECT ...FOR UPDATE`语句,在事务中锁定选中的行,直到事务结束(提交或回滚)才释放锁。其他事务在此期间无法读取(取决于隔离级别)或修改这些数据。- **共享锁(S锁)**:使用`SELECT ...LOCKIN SHARE MODE`语句,允许其他事务读取数据但不允许修改,直到当前事务结束。 3. **适用场景**:-高并发且数据冲突概率高的场景(如库存扣减、账户余额更新等)。-需要确保数据操作严格串行化的场景。4.**注意事项**:-锁的级别:InnoDB默认行级锁,但若条件字段无索引,会升级为表级锁[^3]。-死锁问题:长时间持有锁可能导致死锁,需设置锁等待时(`innodb_lock_wait_timeout`)和开启死锁检测(`innodb_deadlock_detect`)[^5]。现在,我们按照用户要求的结构化方式组织答案,并添加引用标记。</think>### MySQL悲观锁详解:作用、使用方法与适用场景 #### 一、悲观锁的核心作用 悲观锁的核心思想是**“先占坑再办事”**,在操作数据前先加锁,确保操作期间数据不被其他事务修改。主要作用包括: 1. **数据一致性保障**:通过独占锁定防止并发修改冲突[^2] 2. **写操作安全隔离**:确保事务中的读写操作不会被其他事务干扰[^4] 3. **避免丢失更新**:解决高并发场景下的数据覆盖问题[^5] #### 二、使用方法详解(基于InnoDB引擎) ##### 1. 排他锁(X锁) - 最常用 ```sql START TRANSACTION; -- 锁定目标行(需主键或索引) SELECT * FROM accounts WHERE id = 1001 FOR UPDATE; -- 执行数据修改(此时其他事务无法修改此记录) UPDATE accounts SET balance = balance - 500 WHERE id = 1001; COMMIT; ``` **关键点**: - 必须通过`FOR UPDATE`显式声明锁 - 条件字段**必须有索引**,否则会锁整表[^3] - 锁在事务结束时自动释放 ##### 2. 共享锁(S锁) - 读多写少场景 ```sql START TRANSACTION; -- 允许多事务同时读取,禁止修改 SELECT * FROM products WHERE stock > 0 LOCK IN SHARE MODE; -- 其他事务可执行读操作,但无法执行: -- UPDATE products SET stock=10 WHERE id=1; (会被阻塞) COMMIT; ``` ##### 3. 表级锁(慎用) ```sql LOCK TABLES orders WRITE; -- 禁止所有读写 -- 执行批量更新... UPDATE orders SET status = 'shipped' WHERE create_date < '2023-01-01'; UNLOCK TABLES; ``` > ⚠️ 表锁会严重降低并发性,仅用于无索引字段操作或维护任务[^3] #### 三、适用场景分析 | **场景类型** | **推荐锁类型** | **案例说明** | **优势** | |----------------------|----------------|----------------------------------|------------------------------| | 高冲突写操作 | 排他锁(X) | 库存扣减、账户转账[^5] | 避免/透支 | | 数据强一致性要求 | 排他锁(X) | 金融交易、订单状态变更 | 保证事务原子性 | | 读为主+防脏读 | 共享锁(S) | 生成报表时的数据快照 | 允许多读禁止写 | | 全表维护操作 | 表锁 | 历史数据归档、表结构变更 | 避免行锁开销 | #### 四、使用注意事项 1. **索引强制要求** - 无索引的`WHERE`条件会触发**表级锁**,导致并发崩溃[^3] - 解决方案:`ALTER TABLE orders ADD INDEX idx_user(user_id);` 2. **死锁预防** ```sql -- 全局配置(my.cnf) innodb_deadlock_detect = ON -- 死锁检测(默认开启) innodb_lock_wait_timeout = 30 -- 锁等待时(秒)[^5] ``` 最佳实践:事务内**按固定顺序访问表**,如先更新A表再B表 3. **性能权衡** - ✅ 适合场景:短事务、高冲突、强一致性需求 - ❌ 避免场景:长事务(锁持有时间长)、低冲突读写 > **锁选择口诀**: > 高并发写用悲观,低冲突读用乐观 > 金融数据用悲观,统计数据用乐观[^5] #### 五、与乐观锁对比 | **特性** | 悲观锁 | 乐观锁 | |----------------|----------------------------|----------------------| | 并发策略 | 先锁再操作 | 先操作后冲突检测 | | 实现方式 | `FOR UPDATE`/`LOCK TABLES` | 版本号/时间戳 | | 冲突处理 | 阻塞等待 | 事务回滚+重试 | | 典型场景 | 账户扣款 | 商品评论计数 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值