索引

一、使用乐观锁的目的
二、乐观锁实现的方法
三、thinkphp3.2中乐观锁的实现
四、优化thinkphp3.2中的乐观锁

使用乐观锁的目的:

简单的来说,使用乐观锁的目的就是保证数据不会被错误的写入,并且在保护写入的过程中,并不影响其他用户对这个数据的读取(乐观的去读,认为我读的数据都是别人没有改过的)。

乐观锁实现的方法
    乐观锁实现的方法,换句话说就是如何保护数据不被错误的写入?
    举个错误的写入例子,一个教务系统中,某个同学的考试成绩总分数算错了,需要科目A老师扣除总分中的10分,需要科目B的老师扣除总分中的20分;这时科目A的老师和科目B的老师都查阅了该学生的分数是180分,然后科目A的老师扣除10分,输入170分完成修改;科目B的老师扣除20分后输入160完成修改;整个过程完成后,这位同学的分数就变成了160分,实际正确的修改应该是150分才对(180-10-20)。
    那如何保证这位同学的分数被正确的写入呢?这个就是乐观锁实现的方法:提交版本必须大于记录当前版本才能执行更新。为这位同学的分数加个数据版本,这个数据版本就是一个数字,记录当前是第几次修改(数据库保存)。
    php中的实现:每次查询成功时,记录当前数据的版本号,可以使用一个隐藏表单记录(如果是api接口就拿个变量存储一下),再提交修改时,将这个值+1操作过后一起提交过去(数据表要新增一个数据版本字段,用于记录版本),提交过去就存在以下情况:

  1. 提交的数据版本大于数据库版本,满足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,执行更新的操作;
  2. 提交的数据版本小于或者等于当前的数据版本时,不满足提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,说明数据已经发送过更改了,就不允许用户修改,程序提示数据已过期,请重新读取后再操作。

thinkphp3.2中乐观锁的实现
thinkphp3.2中,乐观锁实现的对应代码的位置是:simplewind/Core/Library/Think/Model/AdvModel.class.php
具体代码就不贴出了,简单说一下tp3.2实现的过程:

  1. 新数据插入时,自动插入数据版本对应的值,方法入口:
 // 写入前的回调方法protected function _before_insert(&$data,$options='') {// 记录乐观锁$data = $this->recordLockVersion($data);//..............}

数据插入完成后就用了初始的版本号:0

2.每次查询成功的回调中,缓存当前查询结果行的数据版本值,方法入口:

  // 查询成功后的回调方法protected function _after_find(&$result,$options='') {//..............// 缓存乐观锁$this->cacheLockVersion($result);}

当当前的查询,包含数据版本这个字段时,这个方法就将当前行的主键ID形成一个唯一key,记录数据版本,储存到session中

3.每次执行更新前,检测session是否存在对应的数据版本key,方法入口:

  // 更新前的回调方法protected function _before_update(&$data,$options='') {// 检查乐观锁$pk     =   $this->getPK();if(isset($options['where'][$pk])){$id     =   $options['where'][$pk];   if(!$this->checkLockVersion($id,$data)) {return false;}}//................}

checkLockVersion方法中,就是判断session中一开始查询成功时储存的当前行的数据版本是否与当前模型对应数据库中的数据版本是否一致,当一致时再继续进行更新的操作,并且将数据版本+1提交到修改中;如果不一致就直接返回false,中止修改。

可能存在的问题与优化

tp中乐观锁检测的部分:

 /*** 检查乐观锁* @access protected* @param inteter $id  当前主键     * @param array $data  当前数据* @return mixed*/protected function checkLockVersion($id,&$data) {// 检查乐观锁$identify   = $this->name.'_'.$id.'_lock_version';if($this->optimLock && isset($_SESSION[$identify])) {$lock_version = $_SESSION[$identify];$vo   =  $this->field($this->optimLock)->find($id);$_SESSION[$identify]     =   $lock_version;$curr_version = $vo[$this->optimLock];if(isset($curr_version)) {if($curr_version>0 && $lock_version != $curr_version) {// 记录已经更新$this->error = L('_RECORD_HAS_UPDATE_');return false;}else{// 更新乐观锁$save_version = $data[$this->optimLock];if($save_version != $lock_version+1) {$data[$this->optimLock]  =   $lock_version+1;}$_SESSION[$identify]     =   $lock_version+1;}}}return true;}

可能存在的问题1:
乐观锁验证通过后,立马将session的值改变了,代码:$_SESSION[$identify] = $lock_version+1;
可能存在的问题2:由于更新时,并不具备原子性,可能两个并发的更新会同时执行,均符合乐观锁的版本判断。(并发时,读取出来的版本和数据库均一致,都提交了更新操作)

优化:
解决问题1:
不使用session进行记录,这样操作会使已经读取的记录版本发生混乱,应该将记录值和提交的数据绑定在一起,例如是一次表单提交,查询成功后就使用一个hidden表单,记录当前查询的版本值,和其他数据一并提交;再或者是一个api接口,提交更新时,查询当前行的版本号,和其他数据一并提交;然后再检测乐观锁部分,也就是checkLockVersion($id,&$data)中取出,data中的版本号字段和数据库的版本再进行对比。

解决问题2:
每次执行修改前,加一个redis的锁,可以使用set($redisLock,1,['NX', 'EX' => 10])) 达到原子性,其中的NX参数表示,只有当key不存在时,才设置成功,设置成功才可以进行修改操作的提交,这个操作是具有原子性的,并发进来的修改只有一个修改能成功;如果设置不成功,说明这行记录已经再进行修改的过程中了,我们就可以选择直接结束这个请求,或者让这个请求进行等待;然后再执行完操作过后,删除这个redis锁del($redisLock);即可;

  if (!$this->redis->set($redisLock,1,['NX', 'EX' => $this->redisLockExpire])) {   //设置失败,进行重试addDebugLog('设置redis锁失败;开始重试;重试次数'.($retry_count+1).'数据表:'.$this->name.';数据行:'.$id.';本次操作版本:'.$lock_version);usleep($this->waitTime * 1000000);$retry_count+=1;return  $this->checkLockVersion($id,$data,$retry_count,$lock_version);}

优化总结:成功后的回调不必记录版本号的值,再提交修改时,把当前的数据版本一起提交过去,在修改回调中取出这个提交修改的版本号的值,与数据库对比;每次执行检测时,使用redis中set方法的NX参数,实现更改的原子性。

thinkphp3.2乐观锁源码解读与优化相关推荐

  1. Mutex:互斥锁源码解读

    Mutex count++操作问题 count++是非原子操作,所以有并发问题 什么是原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何 con ...

  2. PyTorch 源码解读之即时编译篇

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者丨OpenMMLab 来源丨https://zhuanlan.zhihu.com/ ...

  3. mysql8.0源代码解析_源码解读:MySQL 8.0 InnoDB无锁化设计的日志系统

    原标题:源码解读:MySQL 8.0 InnoDB无锁化设计的日志系统 作者介绍 张永翔,现任网易云RDS开发,持续关注MySQL及数据库运维领域,擅长MySQL运维,知乎ID:雁南归. MySQL ...

  4. php yii框架源码,yii 源码解读

    date: 2017-11-21 18:15:18 title: yii 源码解读 本篇博客阅读指南: php & 代码提示: 工欲善其事必先利其器 yii 源码阅读指南: 整体上全貌上进行了 ...

  5. xxl-sso源码解读(基于Cookie)

    xxl-sso源码解读 文章目录 xxl-sso源码解读 前言 一.XXL-SSO是什么? 二.搭建步骤 三.系统简述 1.xxl-sso-server 2.xxl-sso-core 3. xxl-s ...

  6. [并发编程] - Executor框架#ThreadPoolExecutor源码解读03

    文章目录 Pre execute源码分析 addWorker()解读 Worker解读 Pre [并发编程] - Executor框架#ThreadPoolExecutor源码解读02 说了一堆结论性 ...

  7. aqs java 简书,Java AQS源码解读

    1.先聊点别的 说实话,关于AQS的设计理念.实现.使用,我有打算写过一篇技术文章,但是在写完初稿后,发现掌握的还是模模糊糊的,模棱两可. 痛定思痛,脚踏实地重新再来一遍.这次以 Java 8源码为基 ...

  8. Alamofire源码解读系列(十二)之请求(Request)

    本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...

  9. Slim 框架源码解读

    0x00 前言 Slim 是由<PHP The Right Way>作者开发的一款 PHP 微框架,代码量不算多(比起其它重型框架来说),号称可以一下午就阅读完(我觉得前提是熟悉 Slim ...

最新文章

  1. Application,Session,Cookie,ViewState和Cache区别
  2. 国内首款商用LCA软件(eBalance)发布公告及培训通知
  3. Docker进阶(制作镜像,共享卷,网络通信,私有仓库)
  4. 【性能优化】 之性能视图及性能参数
  5. Linux 5.4 LVM RAW 设备 配置的深入研究
  6. C#LeetCode刷题之#48-旋转图像(Rotate Image)
  7. 电脑有网络计算机共享怎么用,2台电脑怎么共享文件?没有网络也能共享【详解】...
  8. 以下关于组装微型计算机的叙述 不正确的是,昆明理工大学 计算机系统练习题...
  9. php 抽象类 静态 单体设计模式
  10. python零基础能学吗-Python编程语言好学吗?零基础转行能学Python吗?
  11. JAVA内部类(一)
  12. 电阻电容封装选型经验详解
  13. [华为19实习面试]语言能力优秀的我,是怎么拿下勇敢星实习offer的?华为硬件类面试经历经验分享(大三已拿offer)
  14. 基于Retrofit框架的金山API翻译功能案例
  15. Rockchip | Rockchip Kernel的获取与构建
  16. 【带你快速了解人工智能开发Python基础课程第二周】
  17. 批量提取PDF和图片发票信息 2.2
  18. 用opencv简单绘图
  19. 《重装系统后弹出对话框(无法打开这个应用(无法使用内置管理员账户打开xx,请使用其他账户登录,……))》
  20. 上传IPA包到App Store​

热门文章

  1. dad my_英文绘本 || My Dad!《我爸爸》
  2. 如何挽救婚姻?不想离婚就做好这8个方面,分分钟留下她
  3. 寒江独钓:Windows内核安全编程(china-pub到货首发)
  4. python中的方法是什么_Python方法
  5. Spring data Mongo $map转写用例
  6. python樱花树代码_Python画樱花树
  7. 基于ArcGIS水文分析、HEC-RAS模拟技术在洪水危险性及风险评估
  8. SpringBoot + Java生成证书
  9. C语言学习笔记(3)函数
  10. 如果你是我眼中一滴泪,那么我永远不会哭