"
【关于乐观锁是否安全的问题】
问题如下:
前置条件:数据库中某张表设置字段version,表示更新操作的标志位,int类型。
操作过程:连接DB的connection_thread 先 select 需要update的某条记录并查询出此时记录对应的version 字段,暂存 version 字段,update的同时where条件为 * and version =$version。
问题,假设在高并发场景下两个连接线程同时更改一条记录,查询出相同记录,并且同时获取到这条记录的 version 字段 为 1,然后同时update,会不会出现,update 后的记录效果 为记录version 为 2的 情况。即最后的效果为,两个更改操作合并为同一个更改操作,这样显然不符合正常的业务执行逻辑,比如秒杀过程中出现超卖的问题。
请问大神们,乐观锁是否安全呢。还是说我对乐观锁(CAS)问题了解的有些偏差。
""
比如 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多亿次,显然我的做法更严谨,让乐观锁更有意义.######回复 @乌龟壳 : 你说的情况发生的可能性实在太大了,跟我那个没法比,你没必要在这点上狡辩.你如果不考虑临界点问题,那乐观锁本来就没有了意义."
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。