MySQL乐观锁与悲观锁

这篇具有很好参考价值的文章主要介绍了MySQL乐观锁与悲观锁。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

说明

遇见并发情况,需要保证数据的准确性,也就是与正确的预期一致,此时就会用到锁。
锁是在并发下控制程序的执行逻辑,以此来保证数据按照预期变动。
如果不加锁,并发情况下的可能数据不一致的情况,这是个概率问题。

乐观锁CAS

简介

乐观锁很乐观,假设数据一般情况不会造成冲突,属于程序层面的逻辑锁,在数据进行更新时,才进行锁的检测。是通过添加一个版本号的方式实现的,每当数据这一行所在的数据发生变化,则对应的版本号+1,更新数据时,将版本号作为查询条件。
至于是否要加事务,看写操作单条数据还是写操作多条数据。

注意:网上很多解决方案用时间戳来做version字段,我持反对意见,并发可能是一瞬间的事,不到一秒就有好多请求,用时间戳粒度太大,用随机字符串都比用这个强。

用法

#示例
update test set score = score + 1 where id = 1
#优化为,这种简单,但是会有ABA的问题:
select score as old_score from test where id  = 1;
update test set score = score + 1 where id = 1 and score = old_score;
#或者添加一个version字段,这种不存在ABA的问题
select version from test where id  = 1;
update test set score = score + 1 where id = 1 and version = version;

适用场景

  1. 读多写少:由于并发写操作较少,乐观锁的修改数据受影响行数为0概率也较低。
  2. 允许一定量的重试或不需要重试的场景:这个要根据业务,否则来回重试会降低性能。

优点

实现简单:乐观锁在代码上就可以实现,不需要额外对数据库额外操作。
无死锁风险:悲观锁有死锁风险,乐观锁没有。
无需重试情况下,性能较高:乐观锁机制在并发访问情况下,不需要像悲观锁那样阻塞其他事务,提供了更高的并发性能,前提当前业务需求能容忍写操作失败的情况。

缺点

并发冲突:多加了一个where条件,只能保证数据最终不会出错,不能保证每条写操作的SQL都执行成功(也就是受影响行数>0)。
不提供强一致性:强一致性要求数据的状态在任何时刻都保持一致,悲观锁是到写操作那一步才去验证,期间只是做了个where条件的过滤。
ABA问题:一个字段的值在请求X中查询出来是A,后续代码实现乐观锁,因为并发量大,同时过来一个Y请求,将A值改成了B,因为一些业务原因又改成了A,整个过程虽然不影响请求X的结果,且能正常执行,但是联合其它数据,这个情况是否符合业务场景,不好说,所以最好的解决方案,就是专门做一个version字段,且不会与之前的version重复,即可,把这个version字段作为where条件,而不是存A或者B字段的所在字段作为where条件。

悲观锁

简介

悲观锁比较悲观,假设数据一定会造成冲突,属于MySQL层面的锁。通过加锁阻塞其他事务,悲观锁可以保证在任何时刻,只有一个事务能够修改或访问共享资源,从而实现了强一致性。这意味着在悲观锁机制下,每个事务的读写操作都是有序、线性的。
需要事务的参与。

用法

在事务中的查询语句添加for update即可。

如果此时执行了三行内容没有commit,再次执行update test set score = score + 1 where id = 1;则处于阻塞状态,需要等commit之后,才能执行。
start transaction;
select * from test where id = 1 for update;
update test set score = score + 1 where id = 1;
commit;

适用场景

写多写操作的前提,是保证数据不出错,悲观锁的机制很符合。

优点

强一致性:基于事务又加锁,一致性可以保证。
实现简单:在事务中for update即可,开发者不需要在这上面关注太多。

缺点

死锁风险:悲观锁在使用不当的情况下可能导致死锁。如果多个事务持有锁并相互等待对方释放锁的情况发生,就可能发生死锁。
性能较低:悲观锁通常需要在整个事务过程中锁定资源,这可能导致其他事务阻塞。

模拟实现

前置准备

#创建一个非常简单的表,并插入一条数据
CREATE TABLE `test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `score` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `test` (`id`, `score`) VALUES (1, 0);

需求模拟

查询test表id为1的数据,检测到score值为0,则自增,否则终止。

不加锁实现

为了提升性能,使用了原生PDO操作MySQL去实现。

//连接数据库
$pdo = new \PDO("mysql:host=127.0.0.1;port=3306;dbname=temp;", 'root', 'root');
$pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION);
$pdo->query('set names utf8mb4');

//查询
$query = $pdo->query('select score from test');
$query->setFetchMode(\PDO::FETCH_ASSOC);
$res = $query->fetchALL();


if($res[0]['score'] == 0) {
    $res = $pdo->exec('update test set score = score + 1 where id = 1');
    var_dump($res);
}

并发模拟

用ab压测,发现效果不明显,可能是ab工具不够力或者电脑线程数量太少导致。
这里用的是ApiPost的压测工具。500个并发去多次压测一轮,发现score值是3,证明确实因为并发造成了与预期结果不一致的情况。

乐观锁解决方案(忽略ABA问题)

#将sql改为如下所示,实测多次,score最大值是1
#注意这种行为,只能保证score的值最大是1,无法保证执行这个SQL的时候,受影响行数>0
update test set score = score + 1 where id = 1 and score = 0

悲观锁解决方案

$pdo = new \PDO("mysql:host=127.0.0.1;port=3306;dbname=temp;", 'root', 'root');
$pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION);
$pdo->query('set names utf8mb4');

$redis = new Redis;
$redis->connect('127.0.0.1', 6379);

try {
    $pdo->beginTransaction();

    $stmt = $pdo->prepare("select * from test where id = 1 for update");
    $stmt->execute();
    $res = $stmt->fetch(PDO::FETCH_ASSOC);

    if($res['score'] == 0) {
        $stmt = $pdo->prepare("UPDATE test SET score = (score + 1) where id  = 1");
        $stmt->execute();
        $pdo->commit();
        $redis->incr('commit');
    } else {
        $redis->incr('rollback');
        $pdo->rollBack();
    }
} catch (PDOException $e) {
    $pdo->rollBack();
}

// 关闭数据库连接
$pdo = null;

500个并发压测一轮,查看redis数据,commit数量为1,其余499全部都是rollback,这么多的回滚不代表大错特错(演示效果),而是因为第一个事务执行成功后,再执行其它事务,正因为一个一个排队,就不会出现同时读取多个score值为0的情况了。文章来源地址https://www.toymoban.com/news/detail-819482.html

到了这里,关于MySQL乐观锁与悲观锁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Redis】秒杀业务设计、悲观锁与乐观锁

    一些情境下,使用数据库的ID自增将会产生一些问题。 一方面,自增ID规律性明显,可能被猜测出来并产生一些漏洞 另一方面,当数据量很大很大很大时,单表数据量可能会受到限制,需要分表,多个表之间的ID自增策略受限 测试: Runnable接口是一个函数式接口,即只有一个

    2024年02月13日
    浏览(26)
  • Mysql--技术文档--悲观锁、乐观锁-《控制并发机制简单认知、深度理解》

            首先在谈到并发控制机制的时候,我们通常会提及两种重要的锁策略。悲观锁(Pessimistic Locking)和乐观锁(Optimistic Locking)。这两个是在处理并发的时候采取的不同思路。         悲观锁: 悲观锁机制认为并发操作中会有冲突,因此默认情况下假设会出现并

    2024年02月10日
    浏览(33)
  • JavaEE 初阶篇-深入了解 CAS 机制与12种锁的特征(如乐观锁和悲观锁、轻量级锁与重量级锁、自旋锁与挂起等待锁、可重入锁与不可重入锁等等)

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录         1.0 乐观锁与悲观锁概述         1.1 悲观锁(Pessimistic Locking)         1.2 乐观锁(Optimistic Locking)         1.3 区别与适用场景         2.0 轻量级锁与重量级锁概述         2.1 真正加

    2024年04月16日
    浏览(26)
  • Java并发(十四)----悲观互斥与乐观重试

    1. 悲观互斥 互斥实际是悲观锁的思想 例如,有下面取款的需求 用互斥来保护 2. 乐观重试 另外一种是乐观锁思想,它其实不是互斥

    2024年02月15日
    浏览(31)
  • MySQL悲观锁并发控制实现案例

    实体层 Mapper层 PointMapper.java代码 业务层 web接口层

    2024年02月12日
    浏览(23)
  • 悲观锁&乐观锁

    1.悲观锁 悲观锁介绍(百科): 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层

    2024年02月08日
    浏览(27)
  • django实现悲观锁乐观锁

    前期准备 1.原生mysql悲观锁 2.orm实现上述(悲观锁)  3 乐观锁秒杀--》库存还有,有的人就没成功  

    2024年02月12日
    浏览(32)
  • 悲观锁和乐观锁、缓存

    悲观锁: 悲观锁的实现通常依赖于数据库提供的机制,在整个处理的过程中数据处于锁定状态,session的load方法有一个重载方法,该重载方法的第三个参数可以设置锁模式,load(object.class , int id,LockMode.?),该方法的?就是具体的锁模式。 乐观锁: 乐观锁使用版本号或者时间戳

    2024年02月09日
    浏览(34)
  • 悲观锁和乐观锁(易懂)

    这里可以把悲观锁看作悲观的人,啥事都往最坏的方向想。乐观锁看作乐观的人,啥事都往最好的方向想。 首先,说一下悲观锁。 悲观锁就是假设并发情况下一定会有其他线程来修改数据,因此在处理数据之前,先将数据锁住,确保其他线程不能进行修改 。感觉像一个过于

    2024年02月08日
    浏览(25)
  • 什么是乐观锁和悲观锁?

    乐观锁和悲观锁是并发控制的两种不同策略,用于在多线程环境下管理共享资源的访问。它们有不同的思想和实现方式: 悲观锁(Pessimistic Locking) : 思想 :悲观锁的思想是,它假定在并发访问中会发生冲突,因此在访问共享资源之前会先加锁,以防止其他线程访问。悲观

    2024年02月10日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包