事务
数据库锁的应用需要包含在事务中,如果没有事务,单独在model查询中加入 lock
是无效的。
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"