laravel中锁以及事务的简单使用

news2025/7/19 6:56:18

 一、首先来说一下什么是共享锁?什么是排他锁?

共享:我可以  加锁 , 别人可以  加锁
排他:只有我 才 可以   加锁 , 也就是说,必须要等我提交事务,其他的才可以操作。

二、简单例子实现加锁

 锁和事务在使用时需要配合使用,也就是用锁时需要先开启事务,事务提交时,会自动解锁。

 DB::beginTransaction(); // 开启事务
 $good = \App\Models\Good::sharedLock()->first(); //共享锁 s锁 读锁 
// $good = \App\Models\Good::lockForUpdate()->first(); //排他锁 x锁 写锁...
 DB::commit();  
DB::beginTransaction();
$goodsInfo = Goods::where('goods_id',$gid)->lockForUpdate()->first();
$goodsInfo->seckill_stock-=1;
$goodsInfo->save();
DB::commit();

三、怎样利用锁和事务解决并发问题?

在我们的工作中,常常会出现一些对数量控制有精确要求的需求,比如商品库存量、奖品数量、报名人数限制等等,这些应用场景往往都存在高并发可能,比较容易出现数据量超量问题。以下做一下示例探索:

(1)首先设计一个存量表
CREATE TABLE `product` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_name` varchar(255) NOT NULL DEFAULT '',
  `count` int(10) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
(2)添加一行数据如下,设定基础库存量为 10

(3)问题代码如下:
        $process_num = 50; //开50个进程,模拟50个用户
        for ($i = 0; $i < $process_num; $i++) {
            MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
                if (Db::name('product')->where('id', 1)->value('count') > 0) {
                    $res = Db::name('product')->where('id', 1)->setDec('count');
                    if ($res) {
                        dump('获取到更新资源权限:' . $i);
                    }
                }
            });
        }

执行结果,50 个用户都获取到了更新资源的权限,但是数据库相应数据存量变成了 - 40

高并发带来的问题,同一时刻有多个进程读取同一条数据,同一时刻有多个进程更新同一条数据

(4)解决方案
1.方案1

要进行 DML 层面的限制(最后关卡安全,报错总比出现数据问题产生的影响小),主要的修改是将 count 的类型改成了无符号整数,这样该值就不可能再出现负数值

CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_name` varchar(255) NOT NULL DEFAULT '',
`count` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

执行一下代码,当 count 值从 10 减到 0 时,就不能再减少了,再减就会出现数据库报错

2.方案2

mysql 提供的行级锁 select ... lock in share mode(阻塞写),select ... for update(阻塞读写,悲观锁),所以 for update 机制能满足我们的原子要求。编辑代码如下:

        $process_num = 50; //开50个进程,模拟50个用户
        for ($i = 0; $i < $process_num; $i++) {
            MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
                Db::startTrans(); //行级锁必须在事务中才能生效
                //设置for update,进程会阻塞在这里,只能允许一个进程获取到行锁,其他等待获取
                if (Db::name('product')->where('id', 1)->lock('for update')->value('count') > 0) { 
                    $res = Db::name('product')->where('id', 1)->setDec('count');
                    if ($res) {
                        dump('获取到更新资源权限:' . $i);
                    }
                }
                Db::commit();
            });
        }

只有十个进程获取到了更新权限,消费正常

3.方案3

将条件语句放到 update 上,保持语句执行的原子性,杜绝并发幻读
修改代码如下:

    $process_num = 50; //开50个进程,模拟50个用户
    for ($i = 0; $i < $process_num; $i++) {
        MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
                //合并两条语句为一条更新语句
                $res = Db::name('product')->where('id', 1)->where('count', '>', 0)->setDec('count');
                if ($res) {
                    dump('获取到更新资源权限:' . $i);
                }
        });
    }

只有十个进程获取到了更新权限,消费正常

4.方案4

文件锁机制解决

        $process_num = 50; //开50个进程,模拟50个用户
        for ($i = 0; $i < $process_num; $i++) {
            MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
                $filename = app()->getRootPath() . 'runtime/lock';
                $file = fopen($filename, 'w'); //打开文件
                $lock = flock($file, LOCK_EX);
                // $lock=flock($handle, LOCK_EX|LOCK_NB); (异步非阻塞,所有进程如果出现获取不到锁,不等待跳过,加锁失败)
                //获取文件排他锁:LOCK_EX(异步阻塞,只有一个进程获得锁,其他竞争进程等待)
                //还有一种共享锁:LOCK_SH(所有进程都可以获取共享锁,读取文件,当且只有一个锁时,才允许写操作,否则操作失败,容易出现死锁问题)
                if ($lock) {
                    try {
                        if (Db::name('product')->where('id', 1)->lock('for update')->value('count') > 0) {
                            $res = Db::name('product')->where('id', 1)->setDec('count');
                            if ($res) {
                                dump('获取到更新资源权限:' . $i);
                            }
                        }
                    } catch (\Exception $e) {
                        dump($e->getMessage());
                    } finally {
                        flock($file, LOCK_UN); //无论如何都要释放锁
                    }
                }
                fclose($file); //关闭文件句柄
            });
        }

只有十个进程获取到了更新权限,消费正常

5.方案5

分布式锁机制解决

以上文件锁,只适应于单体架构的需求,在集群架构、分布式等多机联网结构中就是掩耳盗铃了,所以适应性更好地锁机制还是要使用分布式锁,分布式锁最常用和最易用就是 redis 的 setnx 锁了。

        $process_num = 50; //开50个进程,模拟50个用户
        for ($i = 0; $i < $process_num; $i++) {
            MultiProcessHelper::instance($process_num)->multiProcessTask(function () use ($i) {
                //获取redis锁
                //关于CacheHelper::getRedisLock是怎样获取锁的,注意几个点就行:1.如何避免死锁;2.如何设置过期时间;3.如何设置抢占条件;4.如何循环等待判断。这些不在本文讨论范围,可自行研究,以后有空我也可以写一篇博文
                $lock = CacheHelper::getRedisLock('redis_lock');
                if ($lock) {
                    try {
                        if (Db::name('product')->where('id', 1)->lock('for update')->value('count') > 0) {
                            $res = Db::name('product')->where('id', 1)->setDec('count');
                            if ($res) {
                                dump('获取到更新资源权限:' . $i);
                            }
                        }
                    } catch (\Exception $e) {
                        dump($e->getMessage());
                    }
                } else {
//                    dump('获取redis锁失败');
                }
            });
        }

参考链接:浅谈并发加锁 | Laravel China 社区 (learnku.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1102762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于C8051F380的流水灯设计

一、C8051F380简介&#xff1a; C8051F380-GQ 是Silicon Labs的一款高度集成的汽车和工业微控制器 MCU。C8051F380的CPU内核为8051&#xff0c;内核规格: 8 位 , 速度: 48 MIPS &#xff1b; CPU最大主频&#xff1a;192MHz &#xff1b;工作电压范围&#xff1a;2.7V~5.25V &a…

用shell批量修改文件名

场景一 给这些文件都加上.png后缀 #!/bin/bash for i in *; do mv "$i" "$i.png"; done 场景二 给某些文件按某种规则重命名,如按照1,2,3,4…命名,保留原格式 注: Shell中实现整数自增的几种方法示例,此处用于声明是数字类型的declare -i必须添加,否则…

MyBatisPlue-03

一 映射匹配兼容机制 1.映射数据库的字段名 问题描述&#xff1a; 当数据库表字段和实体类的属性名称出现不匹配时&#xff1a; 解决&#xff1a; 知识点&#xff1a; 2.忽略实体类的部分字段 问题描述&#xff1a; 当要表示如 显示用户是否在线等消息&#xff0c;&#xf…

物联网AI MicroPython传感器学习 之 ADXL345 3轴加速度传感器

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 一、产品简介 ADXL345是一款完整的3轴加速度测量系统&#xff0c;可选择的测量范围有士2g&#xff0c;士4g&#xff0c;士8g或士16g。它既能测量运动或冲击导致的动态加速度&#xff0c;也能测量静止加速度&…

C++对象模型(13)-- 构造函数语义学:析构函数

1、默认析构函数生成规则 跟构造函数一样&#xff0c;编译器不一定会为类生成默认析构函数。生成默认析构函数的规则有下面2条&#xff1a; &#xff08;1&#xff09;包含一个类类型的成员变量&#xff0c;且成员变量所属的类有默认析构函数。 &#xff08;2&#xff09;其…

机器学习笔记 - 3D 对象跟踪极简概述

一、简述 大多数对象跟踪应用程序都是 2D 的。但现实世界是 3D 的,无论您是跟踪汽车、人、直升机、导弹,还是进行增强现实,您都需要使用 3D。在 CVPR 2022(计算机视觉和模式识别)会议上,已经出现了大量3D目标检测论文。 二、什么是 3D 对象跟踪? 对象跟踪是指随着时间的…

柔性数组的使用及注意事项

1.柔性数组在结构体当中,并且在结构体的最后面. 2.结构体中除了柔型数组外至少还要有一个其他成员. 3.sizeof()返回结构体的大小不包含柔性数组的大小. 4.malloc 例:struct sdshdr16 *p malloc(sizeof (struct sdshdr16) 32); // 32 为柔性数组的大小 5.free 例: fre…

weblogic任意文件上传漏洞操作(CVE-2018-2894)

运行weblogic&#xff0c;直接找到目录运行&#xff0c;或者cmd命令打开运行 启动之后如下&#xff1a;http://192.168.246.11:7001/console/login/LoginForm.jsp 打开网页http://192.168.0.127:7001/ws_utc/config.do 修改默认路径C:\Oracle\Middleware\Oracle_Home\user_pro…

Express入门指南(详细)

文章目录 &#x1f7e2; Express⭐️ 1.初始Express✨安装✨使用Express 搭建一台服务器 ⭐️2.Express-基本路由✨1.使用字符串模式的路由路径示例&#xff1a;✨2.使用正则表达式的路由路径示例&#xff1a;✨3.中间件浅试(demo) ⭐️3.Express-中间件✨1.应用级中间件✨2.路…

图详解第四篇:单源最短路径--Dijkstra算法

文章目录 1. 最短路径问题2. 单源最短路径--Dijkstra算法算法思想图解如何存储路径及其权值代码实现调式观察打印最短路径Dijkstra算法的缺陷 3. 源码 1. 最短路径问题 最短路径问题&#xff1a; 从带权有向图&#xff08;求最短路径通常是有向图&#xff09;G中的某一顶点出发…

git学习——第4节 时光机穿梭

我们已经成功地添加并提交了一个readme.txt文件&#xff0c;现在&#xff0c;是时候继续工作了&#xff0c;于是&#xff0c;我们继续修改readme.txt文件&#xff0c;改成如下内容&#xff1a; Git is a distributed version control system. Git is free software. 现在&…

甘特图组件DHTMLX Gantt示例 - 如何有效管理团队工作时间?(二)

如果没有有效的时间管理工具&#xff0c;如工作时间日历&#xff0c;很难想象一个项目如何成功运转。这就是为什么我们的开发团队非常重视项目管理&#xff0c;并提供了多种选择来安排DHTMLX Gantt的工作时间。使用DHTMLX Gantt这个JavaScript库&#xff0c;您可以创建一个强大…

python:代码加密

一、将py文件打包成pyc文件 import py_compilepy_compile.compile(rsshare.py)二、机器码加密和解密 示例&#xff1a; 原始机器码输入&#xff1a;‘BRRWQZ8’ 加密机器码输出&#xff1a;‘QlJDDFFSMw’ import base64 # 要加密的字符串 original_string BRRDDL3 # 编码&…

基于R语言实现中介效应检验以及sobel检验代码

数据格式 随机数据&#xff0c;不一定好 y是因变量&#xff0c;x是自变量&#xff0c;m是中介变量 基本原理 M ~ X Y ~ X Y ~ X M 直接上代码 library(mediation) library(bda)# 加载readxl包 library(readxl) # 读取Excel表格# 读取数据 我是从剪切板读取的 data read…

标签页的使用

目录 1、引用TabSheet.h和TabSheet.cpp文件&#xff1a; 2、主窗口添加标签页&#xff1a; &#xff08;1&#xff09;、标签页的创建和属性更改 &#xff08;2&#xff09;、添加俩个标签页的类 &#xff08;3&#xff09;、主窗口添加成员变量 &#xff08;4&#xff09…

[补题记录] Atcoder Beginner Contest 324(E、F)

URL&#xff1a;https://atcoder.jp/contests/abc324 目录 E Problem/题意 Thought/思路 Code/代码 F Problem/题意 Thought/思路 Code/代码 E Problem/题意 给出 N 个字符串和 1 个 T 字符串&#xff0c;都由小写字母组成。 现在从 N 个字符串中任取 2 个拼接&…

电子邮件地址注册过程详解

许多人可能对如何注册电子邮件地址感到困惑&#xff0c;本文将详细解析电子邮件地址的注册过程&#xff1a;确定邮箱厂商、创建邮箱账户、设置电子邮件地址。 1、确定要注册的邮箱厂商 首先我们需要确定要注册哪种类型的电子邮件服务。目前市场上有许多不同的电子邮件服务提供商…

实现日期间的运算——C++

&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️ &#x1f4a5;个人主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王&#x1f525;&#x1f525;&#x1f525; &#x1f4a5;代码仓库&#xff1a;&#x1f525;&#x1f525;魔…

Java内存模型-Java Memory Model(JMM)-可见性、原子性、有序性

5. Java内存模型之JMM 5.1 先从大场面试开始 你知道什么是Java内存模型JMM吗&#xff1f; JMM和volatile他们两个之间的关系&#xff1f; JMM没有那些特征或者它的三大特征是什么&#xff1f; 为什么要有JMM&#xff0c;它为什么出现&#xff1f;作用和功能是什么&#xf…

C++数据结构X篇_15_求二叉树叶子数与高度(递归方法)

本篇参考求二叉树叶子数与高度&#xff08;C&#xff09;进行整理。 文章目录 1. 二叉树中叶子数与高度2. 求二叉树叶子数与高度的实现代码 1. 二叉树中叶子数与高度 我们首先来看一看二叉树中叶子数与高度的定义&#xff1a; 叶子数&#xff1a;对于一个二叉树的节点&#x…