对mysql乐观锁、悲观锁、共享锁、排它锁、行锁、表锁概念的理解

本文详细解释了乐观锁和悲观锁的概念及其应用场景,并通过MySQL示例对比了两种锁的区别,包括共享锁与排它锁的使用。

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

记得在上大学那会开始,在大学的课堂上,常常会听到老师讲什么共享锁,排它锁各种锁的词汇,以前仅仅听过一次就没有管了,并没有进行深入的研究

最近,在各种群里,又看见了什么乐观锁、悲观锁什么鬼的感觉很高级的词汇,于是乎今天对这几个概念进行学习,揭开它神秘的面纱,缕缕思路记录下我对这几个概念的想法


实验环境:

mysql5.6

存储引擎:innoDB


我们在操作数据库的时候,可能会由于并发问题而引起的数据的不一致性(数据冲突)

乐观锁

乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。

举例:

下单操作包括3步骤:

1.查询出商品信息

select (status,status,version) from t_goods where id=#{id}

2.根据商品信息生成订单

3.修改商品status为2

update t_goods 

set status=2,version=version+1

where id=#{id} and version=#{version};


除了自己手动实现乐观锁之外,现在网上许多框架已经封装好了乐观锁的实现,如hibernate,需要时,可能自行搜索"hiberate 乐观锁"试试看。


悲观锁

与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。


共享锁

共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁。相当于对于同一把门,它拥有多个钥匙一样。就像这样,你家有一个大门,大门的钥匙有好几把,你有一把,你女朋友有一把,你们都可能通过这把钥匙进入你们家,进去啪啪啪啥的,一下理解了哈,没错,这个就是所谓的共享锁。
刚刚说了,对于悲观锁,一般数据库已经实现了,共享锁也属于悲观锁的一种,那么共享锁在mysql中是通过什么命令来调用呢。通过查询资料,了解到通过在执行语句后面加上lock in share mode就代表对某些资源加上共享锁了。
比如,我这里通过mysql打开两个查询编辑器,在其中开启一个事务,并不执行commit语句
city表DDL如下:
CREATE TABLE `city` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `state` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;



begin;
SELECT * from city where id = "1"  lock in share mode;

然后在另一个查询窗口中,对id为1的数据进行更新



update  city set name="666" where id ="1";
此时,操作界面进入了卡顿状态,过几秒后,也提示错误信息
[SQL]update  city set name="666" where id ="1";
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction


那么证明,对于id=1的记录加锁成功了,在上一条记录还没有commit之前,这条id=1的记录被锁住了,只有在上一个事务释放掉锁后才能进行操作,或用共享锁才能对此数据进行操作。
再实验一下:



update city set name="666" where id ="1" lock in share mode;
[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1


加上共享锁后,也提示错误信息了,通过查询资料才知道,对于update,insert,delete语句会自动加排它锁的原因

于是,我又试了试SELECT * from city where id = "1" lock in share mode;


这下成功了。




排它锁

排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。
与共享锁类型,在需要执行的语句后面加上for update就可以了


行锁

行锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

比如之前演示的共享锁语句

SELECT * from city where id = "1"  lock in share mode; 

由于对于city表中,id字段为主键,就也相当于索引。执行加锁时,会将id这个索引为1的记录加上锁,那么这个锁就是行锁。


表锁

表锁,和行锁相对应,给这个表加上锁。

MyISAM引擎里有的,暂时研究了




<think>我们正在讨论MySQL中的悲观锁乐观锁。根据引用[1],按照的思想划分,可以分为乐观锁悲观锁。用户希望理解它们的定义、区别以及使用场景。 首先,定义: 1. 悲观锁:总是假设最坏的情况,每次操作数据时都认为别人会修改,所以每次操作时都会上,这样别人想操作这个数据就会阻塞直到它拿到。在MySQL中,典型的实现是通过`SELECT ... FOR UPDATE`语句加排他(X),或者使用`SELECT ... LOCK IN SHARE MODE`加共享锁(S)[^2]。 2. 乐观锁:相对乐观,认为操作数据时不会发生冲突,所以不上。而是在提交更新时,检查在此期间是否有别人修改过数据。通常使用版本号(version)或时间戳来实现。 区别: - 加方式:悲观锁在操作数据前加(数据库层面的);乐观锁则没有实际的加,而是通过版本号控制。 - 使用场景:悲观锁适用于写操作多的场景,保证数据安全;乐观锁适用于读操作多的场景,减少加的开销,提高吞吐量。 - 性能:悲观锁在高并发写时性能较差(因为等待);乐观锁在高并发写时可能冲突较多,导致重试,但读操作性能好。 使用场景: - 悲观锁:当业务逻辑中包含需要严格保证数据一致性的写操作,例如银转账、库存扣减(避免更新丢失,如引用[3]描述的问题)等。 - 乐观锁:适用于读多写少,冲突较少的场景,例如一些并发量不是很大的更新操作,或者可以接受重试的场景。例如,在更新用户信息时,可以通过版本号控制,避免更新覆盖。 下面我们详细说明: ### 悲观锁MySQL中,悲观锁通常通过事务来实现。例如: ```sql BEGIN; SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 加排他 -- 然后进更新操作 UPDATE accounts SET balance = balance - 100 WHERE id = 1; COMMIT; ``` 这样,在事务提交前,其他事务不能修改id=1的记录,从而防止更新丢失。 ### 乐观锁 乐观锁不在数据库层面加,而是在中增加一个版本号字段(如`version`)或时间戳。更新时检查版本号是否改变: ```sql -- 假设有一个version字段 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = old_version; ``` 如果更新数为0,则示版本号已被修改,此次更新失败,需要重试或放弃。 ### 总结 - 悲观锁:强一致,牺牲性能;乐观锁:最终一致,性能较好。 - 选择哪种取决于应用场景:对数据冲突概率高的写操作用悲观锁;冲突概率低的用乐观锁。 §§相关问题§§ 1. 在MySQL中如何具体实现悲观锁? 2. 乐观锁的实现中,除了版本号还有哪些方法? 3. 悲观锁乐观锁在高并发场景下各自的优缺点是什么? 4. 如何避免乐观锁的ABA问题?</think>### MySQL悲观锁乐观锁详解 #### 悲观锁定义与原理 悲观锁(Pessimistic Locking)基于"先加再访问"的假设,认为并发操作很可能会发生冲突。在MySQL中实现方式包括: 1. **排他(X)**:通过`SELECT ... FOR UPDATE`语句实现,阻止其他事务读写[^1] ```sql BEGIN; SELECT * FROM accounts WHERE id=1 FOR UPDATE; -- 加排他 UPDATE accounts SET balance=balance-100 WHERE id=1; COMMIT; ``` 2. **共享锁(S)**:通过`SELECT ... LOCK IN SHARE MODE`实现,允许其他事务读但禁止写[^2] ```sql SELECT * FROM products WHERE id=5 LOCK IN SHARE MODE; ``` #### 乐观锁定义与原理 乐观锁(Optimistic Locking)基于"冲突概率低"的假设,在提交时检测冲突。核心实现方式: 1. **版本号机制**:中增加`version`字段 ```sql UPDATE orders SET status='paid', version=version+1 WHERE id=100 AND version=5; -- 提交时检查版本 ``` 2. **时间戳机制**:使用`last_modified`时间戳字段 ```sql UPDATE inventory SET quantity=quantity-1, last_modified=NOW() WHERE product_id=10 AND last_modified='2023-06-15 10:00:00'; ``` #### 核心区别对比 | 特性 | 悲观锁 | 乐观锁 | |--------------|---------------------------|---------------------------| | 加时机 | 操作前加 | 提交时检查 | | 实现方式 | 数据库原生机制[^1] | 应用层版本控制 | | 并发性能 | 高并发下性能差 | 高并发下性能好 | | 冲突处理 | 阻塞等待 | 回滚/重试机制 | | 适用场景 | 短事务/强一致性要求 | 长事务/高并发读 | | 典型应用 | 银转账[^3] | 商品库存更新 | #### 使用场景分析 **悲观锁适用场景**: 1. 资金交易系统(如转账操作,避免丢失更新[^3]) 2. 票务系统的座位定 3. 需要强一致性的库存扣减(如限量商品) **乐观锁适用场景**: 1. 高并发读多写少场景(如新闻点赞) 2. 业务逻辑允许重试的操作 3. 分布式系统跨数据库事务 4. 长事务操作(如复杂审批流程) #### 实战选择建议 - 当**冲突频率 > 20%** 时选择悲观锁 - 当**系统吞吐量要求高**时优先乐观锁 - 对**数据一致性要求极端严格**的用悲观锁 - **分布式环境**倾向乐观锁+重试机制 > 示例场景:电商库存扣减 > - 悲观锁方案:`FOR UPDATE`定商品,避免超卖 > - 乐观锁方案:版本号更新,失败后前端提示"库存已变化"
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水中加点糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值