【问题标题】:Proper way to create a sequence based on multiple fields基于多个字段创建序列的正确方法
【发布时间】:2018-06-16 03:10:31
【问题描述】:

我正在使用 Laravel 和 Migrations 来构建我的整个数据库结构。

问题描述

在架构中,我有一个pack 表,它属于usergroup,需要为这些表的每个不同组合保留一种唯一的“索引”。

这意味着:基于不同的user_idgroup_id 递增的序列号。例如:

| id | user_id | group_id | sequence |
| 1  | 1       | 1        | 1        |
| 2  | 1       | 2        | 1        |
| 3  | 1       | 3        | 1        |
| 4  | 1       | 1        | 2        |
| 5  | 1       | 2        | 2        |
| 6  | 1       | 3        | 2        |
| 7  | 2       | 1        | 1        |
| 8  | 2       | 2        | 1        |
| 9  | 2       | 3        | 1        |

这将用于引用视图层上的包:

用户 1,这是您的第 1 组的第 1 包。

用户 1,这是第 1 组的第 2 包。

用户 1,这是第 2 组的第 1 包。

我设计了我的迁移(up),例如:

Schema::create('pack', function (Blueprint $table) {
    $table->increments('id');
    $table->integer('user_id')->unsigned();
    $table->foreign('user_id')->references('id')->on('user');
    $table->integer('group_id')->unsigned();
    $table->foreign('group_id')->references('id')->on('group');
    $table->integer('sequence')->unsigned();
});

并使用此业务逻辑在模型层填充$pack->sequence 字段。

问题 1: 从理论上讲,这应该被认为是在所描述的场景中使用的最佳策略?

问题 2: 有一些模式/方法可用于填充数据库层上的sequence 字段?

【问题讨论】:

  • 那么,您想在表的序列列上创建 1,2,3... 值吗?
  • @SagarGautam 是的,基于用户和组的不同组合
  • 您能描述一下应该如何分配序列号吗?
  • @SagarGautam 已经在问题中描述过了,你没有得到哪一点?
  • 哦,我明白了,你在上表中添加的序列是你想要的序列吗?

标签: php mysql database laravel migration


【解决方案1】:

您似乎已经有一个自动增量列id。 MySQL 不支持每个表超过一个自增列。

一般来说,在允许对表进行并发插入时,您无法获得您所描述的行为。原因是您必须读取某些用户/组对的最大序列值,然后在插入新行时插入下一个值。

但这会产生竞争条件,因为其他一些并发会话可能正在做同样的事情,它会潜入并在会话的读取和插入步骤之间插入具有下一个序列值的行。

解决方案是使用锁来防止同时插入相同的 user_id 和 group_id。 InnoDB 将使用间隙锁来解决这个问题。

例子:

打开两个 MySQL 客户端。在第一个会话中,试试这个:

mysql> begin;
mysql> select max(sequence) from pack where user_id=1 and group_id=1 for update;
+---------------+
| max(sequence) |
+---------------+
|             2 |
+---------------+

FOR UPDATE 锁定检查的行,并且它锁定“间隙”,这是插入具有相同 user_id 和 group_id 的其他行的位置。

为了证明这一点,请在第二个会话中尝试:

mysql> begin;
mysql> insert into pack set user_id=1, group_id=1, sequence=3;

挂了。它不能进行插入,因为这与第一个会话仍然持有的间隙锁相冲突。已经避免了竞态条件。

现在在第一个会话中,完成工作。

mysql> insert into pack set user_id=1, group_id=1, sequence=3;
mysql> commit;

提交后注意,会话 1 的锁立即被释放。第二个会话解决了它被阻止的 INSERT,但它正确地得到了一个错误:

ERROR 1062 (23000): Duplicate entry '1-1-3' for key 'user_id'

当然,会话 2 应该执行相同的 SELECT...FOR UPDATE。这也将被阻止,直到它可以解决锁定冲突。解决后,它将返回正确的新最大序列值。

锁仅针对每个 user_id/group_id 组合,当且仅当您有合适的索引。我用过:

ALTER TABLE pack ADD UNIQUE KEY (user_id, group_id, sequence);

一旦您拥有该密钥,SELECT...FOR UPDATE 就能够在锁定正确的行集时指定它们。

这意味着即使 user_id=1, group_id=1 被锁定,您仍然可以为 user_id 或 group_id 的任何其他值插入新条目。它们锁定索引的不同部分,因此没有冲突。

我鼓励您自己做一些实验,向自己证明您了解它的工作原理。您可以在不编写任何 PHP 代码的情况下做到这一点。我刚刚打开了两个终端窗口,运行了 mysql 命令行客户端,然后在mysql> 提示符下开始编写。你也可以!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-25
    • 2017-08-25
    • 1970-01-01
    • 2020-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多