前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Hyperf 实践事务与锁

Hyperf 实践事务与锁

原创
作者头像
黄振炼
发布2023-07-27 19:00:52
5860
发布2023-07-27 19:00:52
举报
文章被收录于专栏:Hcms

事务

数据库锁的应用需要包含在事务中,如果没有事务,单独在model查询中加入 lock 是无效的。

代码语言:javascript
复制
go(function () {
  var_dump("查询用户1 begin-1");
  $user = IotUser::lock()
    ->where('user_id', 1)
    ->first();
  sleep(2);
  var_dump("查询用户1 end-1");
});

go(function () {
  var_dump("查询用户1 begin-2");
  $user = IotUser::lock()
    ->where('user_id', 1)
    ->first();
  sleep(1);
  var_dump("查询用户1 end-2");
});

//打印顺序
// string(21) "查询用户1 begin-1"
// string(21) "查询用户1 begin-2"
// string(19) "查询用户1 end-2"
// string(19) "查询用户1 end-1"

悲观查询锁

在两个查询事务加锁后,虽然另一个的sleep 只有1秒,但是都需要等前一个查询结束

代码语言:javascript
复制
go(function () {
  Db::beginTransaction();
  var_dump("查询用户1 begin-1 " . date("H:i:s"));
  $user = IotUser::lock()
    ->where('user_id', 1)
    ->first();
  sleep(5);
  var_dump("查询用户1 end-1 " . date("H:i:s"));
  Db::commit();
});
sleep(1);
go(function () {
  Db::beginTransaction();
  var_dump("查询用户1 begin-2 " . date("H:i:s"));
  $user = IotUser::lock()
    ->where('user_id', 1)
    ->first();
  sleep(1);
  var_dump("查询用户1 end-2 " . date("H:i:s"));
  Db::commit();
});

//打印结果
// string(30) "查询用户1 begin-1 17:55:50"
// string(30) "查询用户1 begin-2 17:55:51"
// string(28) "查询用户1 end-1 17:55:55"
// string(28) "查询用户1 end-2 17:55:56"

行锁

当我们查询不同的用户ID时候(不同的行数据)时候,我们会发现,并不会触发锁。执行以下代码,我们发现,查询用户2并不会因为查询用户1加锁而影响。

代码语言:javascript
复制
go(function () {
  Db::beginTransaction();
  var_dump("查询用户1 begin-1 " . date("H:i:s"));
  $user = IotUser::lock()
    ->where('user_id', 1)
    ->first();
  sleep(5);
  var_dump("查询用户1 end-1 " . date("H:i:s"));
  Db::commit();
});
sleep(1);
go(function () {
  Db::beginTransaction();
  var_dump("查询用户2 begin-2 " . date("H:i:s"));
  $user = IotUser::lock()
    ->where('user_id', 2)
    ->first();
  sleep(1);
  var_dump("查询用户2 end-2 " . date("H:i:s"));
  Db::commit();
});

//打印结果
// string(30) "查询用户1 begin-1 17:59:11"
// string(30) "查询用户2 begin-2 17:59:12"
// string(28) "查询用户2 end-2 17:59:13"
// string(28) "查询用户1 end-1 17:59:16"

共享锁

查询

共享锁查询,即时是同一行,只要是开通共享锁,彼此的查询是不受影响的。

代码语言:javascript
复制
go(function () {
  Db::beginTransaction();
  var_dump("查询用户1 begin-1 " . date("H:i:s"));
  $user = IotUser::sharedLock()
    ->where('user_id', 1)
    ->first();
  sleep(5);
  var_dump("查询用户1 end-1 " . date("H:i:s"));
  Db::commit();
});
sleep(1);
go(function () {
  Db::beginTransaction();
  var_dump("查询用户1 begin-2 " . date("H:i:s"));
  $user = IotUser::sharedLock()
    ->where('user_id', 1)
    ->first();
  sleep(1);
  var_dump("查询用户1 end-2 " . date("H:i:s"));
  Db::commit();
});

// string(30) "查询用户1 begin-1 18:00:21"
// string(30) "查询用户1 begin-2 18:00:22"
// string(28) "查询用户1 end-2 18:00:23"
// string(28) "查询用户1 end-1 18:00:26"

写入

当写入遇到共享锁的时候,写入会等待共享锁释放才会执行写操作。

代码语言:javascript
复制
go(function () {
  var_dump("查询用户1 begin-1 " . date("H:i:s"));
  Db::beginTransaction();
  $user = IotUser::sharedLock()
    ->where('user_id', 1)
    ->first();
  sleep(5);
  Db::commit();
  var_dump('查询1 updated_at ' . $user->updated_at->toString());
  var_dump("查询用户1 end-1 " . date("H:i:s"));
});
sleep(1);
go(function () {
  var_dump("查询用户1 begin-2 " . date("H:i:s"));
  Db::beginTransaction();
  IotUser::where('user_id', 1)
    ->update(['updated_at' => date("Y-m-d H:i:s")]);
  $user = IotUser::sharedLock()
    ->where('user_id', 1)
    ->first();
  sleep(1);
  Db::commit();
  var_dump('查询2 updated_at ' . $user->updated_at->toString());
  var_dump("查询用户1 end-2 " . date("H:i:s"));
});

lockForUpdate

lockForUpdate 针对有写入操作时候,先等写入操作执行完毕在在进行读操作,保证数据都是最新数据。

注意,这时候价格 lockForUpdate 的查询,没有加入事务。

代码语言:javascript
复制
go(function () {
  var_dump("查询用户1 pre-1 " . date("H:i:s"));
  sleep(3);
  var_dump("查询用户1 begin-1 " . date("H:i:s"));
  $user = IotUser::lockForUpdate()
    ->where('user_id', 1)
    ->first();
  sleep(2);
  
  var_dump('查询1 updated_at ' . $user->updated_at->toString());
  var_dump("查询用户1 end-1 " . date("H:i:s"));
});
sleep(1);
go(function () {
  var_dump("写入用户1 pre-2 " . date("H:i:s"));
  Db::beginTransaction();
  var_dump("写入用户1 begin-2 " . date("H:i:s"));
  IotUser::where('user_id', 1)
    ->update(['updated_at' => date("Y-m-d H:i:s")]);
  $user = IotUser::sharedLock()
    ->where('user_id', 1)
    ->first();
  sleep(3);
  Db::commit();
  var_dump('写入2 updated_at ' . $user->updated_at->toString());
  var_dump("查询用户1 end-2 " . date("H:i:s"));
});

// string(28) "查询用户1 pre-1 18:31:13"
// string(28) "写入用户1 pre-2 18:31:14"
// string(30) "写入用户1 begin-2 18:31:14" //开始写入
// string(30) "查询用户1 begin-1 18:31:16" //查询也开始,但是发现有写入在操作,没有执行查询
// string(52) "写入2 updated_at Thu Apr 20 2023 18:31:14 GMT+0800"
// string(28) "查询用户1 end-2 18:31:17" //等到写入完成,才执行原有的查询,这是查出来就是最新的
// string(52) "查询1 updated_at Thu Apr 20 2023 18:31:14 GMT+0800"
// string(28) "查询用户1 end-1 18:31:19"

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 事务
  • 悲观查询锁
  • 行锁
  • 共享锁
    • 查询
      • 写入
      • lockForUpdate
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档