使用 Laravel sharedLock 与 lockForUpdate 进行数据表行锁「建议收藏」

使用 Laravel sharedLock 与 lockForUpdate 进行数据表行锁

大家好,又见面了,我是全栈君。

场景

拼团功能,当 A 客户开团之后(两人团),如果 B 和 C 同时支付,如何规避两人同时将拼团人数增加。

Laravel 中 sharedLock 与 lockForUpdate 的区别

  • sharedLock 对应的是 LOCK IN SHARE MODE

  • lockForUpdate 对应的是 FOR UPDATE

sharedLock 与 lockForUpdate 相同的地方是,都能避免同一行数据被其他 transaction 进行 update。

不同的地方是:

  • sharedLock 不会阻止其他 transaction 读取同一行

  • lockForUpdate 会阻止其他 transaction 读取同一行 (需要特别注意的是,普通的非锁定读取读取依然可以读取到该行,只有 sharedLock 和 lockForUpdate 的读取会被阻止。)

即 sharedLock locks only for write, lockForUpdate also prevents them from being selected

这样做是有意义的,例如,两个 transaction 要更新同一个计数器,如果不使用 lockForUpdate, 会导致两个 transaction 同时读到同一个初始值,然后在应用层逻辑中增加计数之后,提交到数据库中,后者的操作会覆盖掉前者的操作。

如何测试

在 MySQL 命令行终端操作一个表

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from users for update;
+----+------+
| id | name |
+----+------+
|  1 | tom  |
|  2 | bob  |
+----+------+

这时再开一个命令行终端

mysql> select * from users for update;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from users lock in share mode;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted

你会发现,无论是 for update 还是 lock in share mode 都无法读取到数据,更加确切地说是,查询被阻塞了。

只有在第一个终端执行

commit;

第二个终端才能得到数据返回。

需要注意的是,发起者必须在 transaction 里上锁才有效,如果不是在 transaction 中,上锁是无效的。但是,第二个人无论是不是在 transaction 里,都会被锁。

我依然有几个疑问

  • Laravel 如何设置数据库操作超时时间

  • 什么场景下适合使用 sharedLock 呢?

  • sharedLock,lockForUpdate 与 Pessimistic Locking 是什么关系

  • Pessimistic locking(悲观锁) 与 Optimistic locking(乐观锁)的区别

如何测试 Laravel

A 用户,在浏览器里访问接口 (模拟支付回调),此时对数据表中某一行锁住,进行 30s 操作,然后提交事务。

B 用户,在浏览器里访问同一接口 (模拟支付回调),其无法修改该行。对应的返回是什么?

会一直 wait 到数据库操作超时。

那么问题来了,Laravel 如何设置数据库操作超时时间?

简单的测试方法,是在命令行中开两个 artisan tinker 窗口,分别执行

DB::transaction(function () {
	echo 1;
	User::where('id', 33)->lockForUpdate()->get();
	echo 2;
	sleep(10);
});

你会发现第二个 tinker 窗口中的 get 操作,需要等到第一个 transaction 执行完毕之后,才能得到查询结果。

需要注意的是,不在 transaction 中的 lockForUpdate 操作,是没有锁效果的。

真实场景,防止用户重复提现

DB::transaction(function () use ($user, &$user_award) {
            $user_award = UserAward::where([
                    ['user_id', $user->id],
                    ['status', 0],
                ])
                ->lockForUpdate()
                    ->first();
            if ($user_award) {
                $user_award->status = 1;        // 提现中状态
                $user_award->save();
            }
});

if (!is_null($user_award)) {
       $amount = $user_award->money * 100;
}

事务与锁的关系

事务中涉及的操作都会加上锁?

如果默认会加上锁,那么默认会加上什么锁呢?

事务中涉及的操作,不需要显式加锁?

要理清其中关系,就需要了解事务的四种隔离级别:

  • 未提交读(Read uncommitted)

  • 已提交读(Read committed)

  • 可重复读(Repeatable read)

  • 可串行化(Serializable )

MySQL 默认的是:可重复读(Repeatable read)

使用 Laravel sharedLock 与 lockForUpdate 进行数据表行锁「建议收藏」

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/111769.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 让王老吉崛起的三大营销战役「建议收藏」

    让王老吉崛起的三大营销战役「建议收藏」一个存在了百年的区域市场老品牌,在短短5年时间内崛起,迅速成长为具有全国影响力的饮料销售冠军,而且博得了良好的社会美誉,这其中有什么不为人知的营销实践?又有着怎样的成长秘诀?梳理王老吉的发展史,我们注意到,有三大营销战役决定了这个品牌的崛起。撷取这三次营销个案,不但可以让我们知道王老吉的崛起密码,还可以了解到网络营销、品牌定位、产品延伸等营销前沿思想的现实实践。一个“网络营销”的经典案例?如

    2022年10月25日
    0
  • SQL中row_number() over(partition by)详解「建议收藏」

    SQL中row_number() over(partition by)详解「建议收藏」row_number语法ROW_NUMBER()函数将针对SELECT语句返回的每一行,从1开始编号,赋予其连续的编号。在查询时应用了一个排序标准后,只有通过编号才能够保证其顺序是一致的,当使用ROW_NUMBER函数时,也需要专门一列用于预先排序以便于进行编号partitionby关键字是分析性函数的一部分,它和聚合函数不同的地方在于它能返回一个分组中的多条记录,而聚合函数一般只有一条反…

    2022年6月14日
    26
  • ACM/ICPC2014鞍山现场赛E hdu5074Hatsune Miku「建议收藏」

    ACM/ICPC2014鞍山现场赛E hdu5074Hatsune Miku

    2022年1月28日
    50
  • html滚动字幕制作教程,dreamweaver教程:怎么制作网页滚动字幕[通俗易懂]

    html滚动字幕制作教程,dreamweaver教程:怎么制作网页滚动字幕[通俗易懂]滚动字幕在FrontPage的组件里有,但是FrontPage这个软件只能支持单行文字,一出现多行文字它就无能为力了,而且它只能支持一行滚动!(如果出现只能滚动一行的情况,解决办法是把这段代码嵌入到JavaScript的document.write里面,请看下面例的详细说明)Dreamweaver也只能用编写HTML代码的方法。所以强烈建议用记事本打开网页源代码来编辑。1.建立第一个滚动字幕。代码…

    2022年6月9日
    35
  • Python中sys模块

    sys-系统特定的参数和功能该模块提供对解释器使用或维护的一些变量的访问,以及与解释器强烈交互的函数。它始终可用。sys.argv传递给Python脚本的命令行参数列表。argv[0]是脚本名称(依赖于操作系统,无论这是否是完整路径名)。如果使用-c解释器的命令行选项执行命令,argv[0]则将其设置为字符串’-c’。如果没有脚本名称传递给Python解释器,argv[0]则为空字符串…

    2022年4月5日
    48
  • js入门——Dom基础

    js入门——Dom基础

    2022年1月25日
    36

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号