Hyperf 实践事务与锁

2023-07-27 19:00:52 浏览数 (2)

事务

数据库锁的应用需要包含在事务中,如果没有事务,单独在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"

0 人点赞