开发者社区> 问答> 正文

关于乐观锁问题 - 乐观锁报错

"

【关于乐观锁是否安全的问题】

问题如下:

前置条件:数据库中某张表设置字段version,表示更新操作的标志位,int类型。

操作过程:连接DB的connection_thread 先 select 需要update的某条记录并查询出此时记录对应的version 字段,暂存 version 字段,update的同时where条件为 * and version =$version。

问题,假设在高并发场景下两个连接线程同时更改一条记录,查询出相同记录,并且同时获取到这条记录的 version  字段 为 1,然后同时update,会不会出现,update 后的记录效果 为记录version 为 2的 情况。即最后的效果为,两个更改操作合并为同一个更改操作,这样显然不符合正常的业务执行逻辑,比如秒杀过程中出现超卖的问题。

请问大神们,乐观锁是否安全呢。还是说我对乐观锁(CAS)问题了解的有些偏差。

"

展开
收起
montos 2020-06-04 13:18:38 1007 0
1 条回答
写回答
取消 提交回答
  • "

    比如 MySQL,InnoDB,行级锁;

    不会出现同时 Update 成功的情况,乐观锁 update 带上了 version 条件,更新不到记录时受影响记录数为 0,后续做相关的业务处理。

    ######ok,我陷入可一个误区,我知道了,是安全的,如果不在其他业务逻辑进行表操作的情况下。######

    把版本号当做条件,第二个update 的时候是更新不到记录的,受影响条数是 0,然后抛出异常,回滚事务。

    ######如果两个线程同时获取到相同的version字段,两个同时update ,当前数据记录中version字段值 == 查询出的暂存version字段条件成立,会不会同时更新为相同的结果,最终version字段只是+1?######

    Update的时候innoDB会行级锁吧    乐观锁之后第二个应该是影响0行吧   不会报错的 

    ######

    就算2个或者多个线程同时update,那么数据就出现了不确定性,可能是第一个线程update的数据,也可能是后面线程的数据,你说的+1情况,不明白为什么会出现!

    ######

    不会的,你第一个update后version+1了,后一个匹配不到数据

    ######

    可信答案:

    ######

    这里抛出一个相关的问题:
    @乌龟壳
    version字段一直在自增,当其自增到最大值时,这个临界点该如何处理?
    我的做法是引入一个字段flag,用于标记version应该自增还是自减.
    乐观锁常用于并发冲突少,同时又要保证正确读写的场景.
    我把乐观锁用到了程序读写用户会话session表上:

    <?php
    $table = $app['db_prefix'].'session';
    switch(true) {
    	//version 类型 smallint 范围 0 到 65535
    	case ($app['user']['session_flag'] == 1 && $app['user']['session_version'] != 65535):
    		$sql = "UPDATE `{$table}` SET `session` = ?, `version` = ? 
    		WHERE `user_id` = ? AND `version` = ? AND `flag` = 1";
    		$version_increase = true;
    		break;
    	case ($app['user']['session_flag'] == 1 && $app['user']['session_version'] == 65535):
    		$sql = "UPDATE `{$table}` SET `session` = ?, `version` = ?, `flag` = -1 
    		WHERE `user_id` = ? AND `version` = ? AND `flag` = 1";
    		$version_increase = false;
    		break;
    	case ($app['user']['session_flag'] == -1 && $app['user']['session_version'] != 0):
    		$sql = "UPDATE `{$table}` SET `session` = ?, `version` = ? 
    		WHERE `user_id` = ? AND `version` = ? AND `flag` = -1";
    		$version_increase = false;
    		break;
    	case ($app['user']['session_flag'] == -1 && $app['user']['session_version'] == 0):
    		$sql = "UPDATE `{$table}` SET `session` = ?, `version` = ?, `flag` = 1 
    		WHERE `user_id` = ? AND `version` = ? AND `flag` = -1";
    		$version_increase = true;
    		break;
    }
    $stmt = $db->prepare($sql);
    $stmt->execute(array(
    	$session,
    	$version_increase ? $app['user']['session_version'] + 1 : $app['user']['session_version'] - 1,
    	$app['user']['id'],
    	$app['user']['session_version'],
    ));
    return ($stmt->rowCount() == 0) ? false : true;

     

    ######楼主的头像都被吓黑了######回复 @eechen : 你加一个flag,本质就是用true和false两种状态,配合version字段,组合成最终比smallint还要多两倍的可能的值。实际上你自己造了一个数据类型而已。######回复 @eechen : 到了溢出值回0,和你说的倒退什么的,有啥区别?你只不过用一个flag把可能的值加了一倍而已,但是代码量更大。如果你强调smallint本身的值空间可能不够用,把version字段数据类型设为bigint就能增加何止上亿倍的值空间了,这样不也是简单的溢出就设0就能解决吗?######回复 @乌龟壳 : 你说的情况多个请求到达临界点就可能发生,而我说的配合flag做法,得跨过80多亿次,显然我的做法更严谨,让乐观锁更有意义.######回复 @乌龟壳 : 你说的情况发生的可能性实在太大了,跟我那个没法比,你没必要在这点上狡辩.你如果不考虑临界点问题,那乐观锁本来就没有了意义."
    2020-06-04 13:30:57
    赞同 展开评论 打赏
问答地址:
问答排行榜
最热
最新

相关电子书

更多
低代码开发师(初级)实战教程 立即下载
冬季实战营第三期:MySQL数据库进阶实战 立即下载
阿里巴巴DevOps 最佳实践手册 立即下载