mysql悲观锁与乐观锁

本文深入探讨了数据库管理系统(DBMS)中的并发控制技术,包括悲观锁和乐观锁的工作原理、应用场景及优缺点,通过实例展示了MySQL InnoDB中悲观锁的实现过程。

简介

   数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。

用途

   乐观锁和悲观锁是并发控制主要采用的技术手段。无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是数据库系统中有乐观锁和悲观锁的概念,像 memcache、hibernate、tair 等都有类似的概念。针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为 DBMS 中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在 DBMS 中,悲观锁正是利用数据库本身提供的锁机制来实现的。

 

悲观锁

     在关系数据库管理系统里,悲观并发控制(又名 “悲观锁”,Pessimistic Concurrency Control,缩写 “PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作都某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

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

    在数据库中,悲观锁的流程如下:
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

 

MySQL InnoDB 中使用悲观锁

要使用悲观锁,我们必须关闭 mysql 数据库的自动提交属性,因为 MySQL 默认使用 autocommit 模式,也就是说,当你执行一个更新操作后,MySQL 会立刻将结果进行提交。 set autocommit=0;

    //1.开始事务            
    begin;/begin work;/start transaction; (三者选一就可以)              
    //2.查询出商品信息               
    select status from t_goods where id=1 for update;              
    //3.根据商品信息生成订单                
    insert into t_orders (id,goods_id) values (null,1);              
    //4.修改商品status为2               
    update t_goods set status=2;              
    //5.提交事务             
    commit;/commit work;   

       上面的查询语句中,我们使用了 select…for update 的方式,这样就通过开启排他锁的方式实现了悲观锁。此时在 t_goods 表中,id 为 1 的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

       上面我们提到,使用 select…for update 会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB 默认行级锁。行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

优点与不足展开目录

       悲观并发控制实际上是 “先取锁再访问” 的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数。

 

乐观锁

       在关系数据库管理系统里,乐观并发控制(又名 “乐观锁”,Optimistic Concurrency Control,缩写 “OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。乐观事务控制最早是由孔祥重(H.T.Kung)教授提出。

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

数据版本, 为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

使用版本号实现乐观锁

     使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将 version 字段的值一同读出,数据每更新一次,对此 version 值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的 version 值进行比对,如果数据库表当前版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据

示例

  • 数据库表设计
    task 表有三个字段,分别是 id,value、version
  • 实现

1. 先读 task 表的数据(实际上这个表只有一条记录),得到 version 的值为 versionValue
2. 每次更新 task 表中的 value 字段时,为了防止发生冲突,需要这样操作

update task set value = newValue,version =  versionValue + 1   where version = versionValue;

只有这条语句执行了,才表明本次更新 value 字段的值成功

如假设有两个节点 A 和 B 都要更新 task 表中的 value 字段值,差不多在同一时刻,A 节点和 B 节点从 task 表中读到的 version 值为 2,那么 A 节点和 B 节点在更新 value 字段值的时候,都操作

update task set value = newValue,version = 3 where version = 2;

实际上只有 1 个节点执行该 SQL 语句成功,假设 A 节点执行成功,那么此时 task 表的 version 字段的值是 3,B 节点再操作

update task set value = newValue,version = 3 where version = 2;

这条 SQL 语句是不执行的,这样就保证了更新 task 表时不发生冲突。

 

转载于:https://www.cnblogs.com/jian-ge/p/10119995.html

<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`锁定商品行,避免超卖 > - 乐观锁方案:版本号更新,失败后前端提示"库存已变化"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值