3

我正在使用 Laravel 5.1 并尝试在用户打开记​​录编辑视图时实现记录锁定,以便任何其他用户在释放锁定之前无法打开同一记录的编辑视图。

很明显,用户永远不会完成编辑,所以我需要能够自动解锁记录(当用户进行任何其他事务时,或者在超时事件之后,或者使用 ajax 后台保持活动调用时)。

我已经查看lockForUpdate()并阅读了有关 InnoDB 锁定功能的信息,但我无法获得有关它的真实信息(搜索了 20 多个帖子,它们似乎都在没有真实信息的情况下相互引用)。文档也没有提供太多信息,探索 Laravel 查询类代码让我取得了以下进展:

  1. 头痛。
  2. 我意识到它仅在查询生成器中可用(我需要它用于 Eloquent)。
  3. 似乎它只是跨国锁定而不是签入/签出记录锁定。

似乎术语和mysql(innodb)锁定的实际作用都存在很大的混淆,或者可能只是我试图弄清楚这一点。

长话短说,InnodDB/Laravel 锁定的主要警告(根据我的需要)是它不是持久的,因此当 php 脚本终止并且用户仍在编辑表单时它会自行重置。它仅用于交易。

我需要实现上述功能(编辑签入/签出),在重新发明轮子之前,我很想知道是否已经有内置的 Laravel/PHP 功能。

如果不是,我正在考虑以下方法,并希望获得关于哪种方法最好的输入。

A. 创建一个edit_checkins带有 record_id、user_id、timestamp 的表。

B.locked_at向记录表添加一列,该列将包含时间戳或空值(类似于deleted_at列)。

我主要关心的是性能和“垃圾收集”,因为有时记录被锁定但从未主动解锁。edit_checkins通过这种方法,人工智能可以在一个简单的查询中从表中删除所有用户锁。但是,检查记录是否被锁定时会慢一些,因为我必须进行表连接(我认为它应该可以忽略不计,因为编辑事件比其他事件少)。使用方法 B 可以更快地检查,但我没有获得所有信息(例如 user_id),并且在不知道 user_id 的情况下实现解锁所有事件几乎是不可能的(我可能需要在记录中添加一locked_by列)。

我希望我把问题说得够清楚了。感谢您的时间。

4

1 回答 1

9

您正在尝试做的是对记录的应用程序级锁定。您有一个业务级别要求,即一次只能有一个用户查看记录的编辑视图。这种“锁定”可以持续几秒钟、几分钟、几小时,或者你希望允许的任何最大超时。

这与记录上的数据库级锁完全不同。需要数据库级锁来确保两个更新语句不会同时在同一记录上运行。这个锁只会持续到事务需要的时间,通常只有几毫秒。长时间运行或开放式数据库事务不是一个好主意。

您将需要设计自己的应用程序逻辑来做您想做的事。我没有看到任何现有的 Laravel 包。有一个叫做laravel-record-lock,但它不会做你想做的事情,它不会在多个请求中保持锁。

我认为最灵活的设计是创建一个record_locks表,然后与您想要的任何模型创建多态关系lockable多态关系文档。为了让你开始:

数据库表:

record_locks
    - id
    - timestamps (if you want)
    - lockable_id - integer
    - lockable_type - string
    - user_id - integer
    - locked_at - datetime/timestamp

模型

class RecordLock extends Model
{
    /**
     * Polymorphic relationship. Name of the relationship should be
     * the same as the prefix for the *_id/*_type fields.
     */
    public function lockable()
    {
        return $this->morphTo();
    }

    /**
     * Relationship to user.
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }

    // additional functionality
}

现在您可以将多态关系添加到您想要锁定的任何模型:

class Book extends Model
{
    /**
     * Polymorphic relationship. Second parameter to morphOne/morphMany
     * should be the same as the prefix for the *_id/*_type fields.
     */
    public function recordLock()
    {
        return $this->morphOne('App\RecordLock', 'lockable');
    }
}

class Car extends Model
{
    /**
     * Polymorphic relationship. Second parameter to morphOne/morphMany
     * should be the same as the prefix for the *_id/*_type fields.
     */
    public function recordLock()
    {
        return $this->morphOne('App\RecordLock', 'lockable');
    }
}

最后,用作常规关系:

$book = \App\Book::first();
$lock = $book->recordLock; // RecordLock object or null

$car = \App\Car::first();
$lock = $car->recordLock; // RecordLock object or null

/**
 * Accessing the relationship from the RecordLock object will
 * dynamically return the type of object that was locked.
 */

$lock = \App\RecordLock::find(1);
$lockedObject = $lock->lockable; // \App\Book object

$lock = \App\RecordLock::find(2);
$lockedObject = $lock->lockable; // \App\Car object

最后一个旁注来解决您对查询构建器与 Eloquent 的担忧:Eloquent 模型回退到 Eloquent 查询构建器,而 Eloquent 查询构建器回退到普通查询构建器。如果您在 Eloquent 模型上调用方法,它会尝试在 Eloquent 查询构建器上调用它。如果它在那里不存在,它会尝试在普通的查询生成器上调用它。如果那里不存在,你会得到一个错误。

所以,如果你这样做\App\User::lockForUpdate(),方法调用最终会过滤到普通的查询构建器(因为它在 Eloquent 模型或 Eloquent 查询构建器上不存在)。

于 2015-08-15T07:41:03.300 回答