简答
第三个选项:Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement
$teamMorphType = Relation::getMorphedModel('team');
$groupMorphType = Relation::getMorphedModel('group');
$formMorphType = Relation::getMorphedModel('form');
$permissible = [
$teamMorphType => [$user->team_id],
$groupMorphType => [],
$formMorphType => [],
];
foreach ($user->permissible as $permissible) {
switch ($permissible->permissible_type) {
case $teamMorphType:
case $groupMorphType:
case $formMorphType:
$permissible[$permissible->permissible_type][] = $permissible->permissible_id;
break;
}
}
$forms = Form::query()
->where('user_id', '=', $user->id)
->orWhereIn('id', $permissible[$fromMorphType])
->orWhereIn('team_id', $permissible[$teamMorphType])
->orWhereIn('group_id', $permissible[$groupMorphType])
->get();
长答案
一方面,(几乎)您可以在代码中执行的所有操作在性能方面都比在查询中执行的要好。
另一方面,从数据库中获取比必要更多的数据已经是太多的数据(RAM 使用等)。
从我的角度来看,您需要介于两者之间,只有您自己知道平衡点在哪里,具体取决于数字。
我建议运行几个查询,您提出的最后一个选项 (Query all identifiers for all permissions (5), then query the Form model using the identifiers in an IN() statement):
- 查询所有标识符,获取所有权限(5 个查询)
- 合并内存中的所有表单结果,获取唯一值
array_unique($ids)
- 使用 IN() 语句中的标识符查询 Form 模型。
您可以尝试您提出的三个选项并监控性能,使用某些工具多次运行查询,但我 99% 确信最后一个选项会给您最好的性能。
这也可能会发生很大变化,具体取决于您使用的数据库,但如果我们谈论的是 MySQL,例如;在一个非常大的查询中会使用更多的数据库资源,这不仅会比简单的查询花费更多的时间,而且还会锁定表的写入,这会产生死锁错误(除非你使用从服务器)。
另一方面,如果表单 id 的数量非常多,占位符过多可能会出错,因此您可能希望将查询分块,例如 500 个 id(这取决于很多,因为限制是大小,而不是绑定数量),并将结果合并到内存中。即使您没有遇到数据库错误,您也可能会看到性能上的巨大差异(我仍然在谈论 MySQL)。
实施
我假设这是数据库方案:
users
- id
- team_id
forms
- id
- user_id
- team_id
- group_id
permissible
- user_id
- permissible_id
- permissible_type
所以允许的是已经配置的polymorphic relationship。
因此,关系将是:
- 拥有表格:
users.id <-> form.user_id
- 团队拥有表格:
users.team_id <-> form.team_id
- 对拥有表单的组具有权限:
permissible.user_id <-> users.id && permissible.permissible_type = 'App\Team'
- 对拥有表单的团队具有权限:
permissible.user_id <-> users.id && permissible.permissible_type = 'App\Group'
- 拥有表单权限:
permissible.user_id <-> users.id && permissible.permissible_type = 'App\From'
简化版:
$teamMorphType = Relation::getMorphedModel('team');
$groupMorphType = Relation::getMorphedModel('group');
$formMorphType = Relation::getMorphedModel('form');
$permissible = [
$teamMorphType => [$user->team_id],
$groupMorphType => [],
$formMorphType => [],
];
foreach ($user->permissible as $permissible) {
switch ($permissible->permissible_type) {
case $teamMorphType:
case $groupMorphType:
case $formMorphType:
$permissible[$permissible->permissible_type][] = $permissible->permissible_id;
break;
}
}
$forms = Form::query()
->where('user_id', '=', $user->id)
->orWhereIn('id', $permissible[$fromMorphType])
->orWhereIn('team_id', $permissible[$teamMorphType])
->orWhereIn('group_id', $permissible[$groupMorphType])
->get();
详细版本:
// Owns Form
// users.id <-> forms.user_id
$userId = $user->id;
// Team owns Form
// users.team_id <-> forms.team_id
// Initialise the array with a first value.
// The permissions polymorphic relationship will have other teams ids to look at
$teamIds = [$user->team_id];
// Groups owns Form was not mention, so I assume there is not such a relation in user.
// Just initialise the array without a first value.
$groupIds = [];
// Also initialise forms for permissions:
$formIds = [];
// Has permissions to a group that owns a Form
// permissible.user_id <-> users.id && permissible.permissible_type = 'App\Team'
$teamMorphType = Relation::getMorphedModel('team');
// Has permissions to a team that owns a Form
// permissible.user_id <-> users.id && permissible.permissible_type = 'App\Group'
$groupMorphType = Relation::getMorphedModel('group');
// Has permission to a Form
// permissible.user_id <-> users.id && permissible.permissible_type = 'App\Form'
$formMorphType = Relation::getMorphedModel('form');
// Get permissions
$permissibles = $user->permissible()->whereIn(
'permissible_type',
[$teamMorphType, $groupMorphType, $formMorphType]
)->get();
// If you don't have more permissible types other than those, then you can just:
// $permissibles = $user->permissible;
// Group the ids per type
foreach ($permissibles as $permissible) {
switch ($permissible->permissible_type) {
case $teamMorphType:
$teamIds[] = $permissible->permissible_id;
break;
case $groupMorphType:
$groupIds[] = $permissible->permissible_id;
break;
case $formMorphType:
$formIds[] = $permissible->permissible_id;
break;
}
}
// In case the user and the team ids are repeated:
$teamIds = array_values(array_unique($teamIds));
// We assume that the rest of the values will not be repeated.
$forms = Form::query()
->where('user_id', '=', $userId)
->orWhereIn('id', $formIds)
->orWhereIn('team_id', $teamIds)
->orWhereIn('group_id', $groupIds)
->get();
使用的资源:
数据库性能:
- 对数据库的查询(不包括用户):2;一个获得许可,另一个获得表格。
- 没有加入!!
- 可能的最小 OR (
user_id = ? OR id IN (?..) OR team_id IN (?...) OR group_id IN (?...).
PHP,在内存中,性能:
-
foreach 使用 switch 在内部循环允许。
-
array_values(array_unique()) 避免重复 ID。
- 在内存中,3 个 id 数组(
$teamIds、$groupIds、$formIds)
- 在内存中,相关权限 eloquent 集合(这个可以优化,如果需要的话)。
优点和缺点
优点:
-
时间:单个查询的时间总和小于连接和 OR 的大查询的时间。
-
DB 资源:带有 join 和 or 语句的查询使用的 MySQL 资源大于其单独查询的总和。
-
金钱:更少的数据库资源(处理器、RAM、磁盘读取等),比 PHP 资源更昂贵。
-
锁:如果你不是在查询一个只读的从服务器,你的查询会产生更少的行读锁(读锁在 MySQL 中是共享的,所以它不会锁定另一个读,但是它将阻止任何写入)。
-
可扩展:这种方法允许您进行更多的性能优化,例如对查询进行分块。
缺点:
-
代码资源:在代码中而不是在数据库中进行计算,显然会在代码实例中消耗更多的资源,尤其是在存储中间信息的RAM中。在我们的例子中,这只是一个 id 数组,这应该不是问题。
-
维护:如果你使用 Laravel 的属性和方法,并且你在数据库中进行任何更改,那么在代码中更新将比你进行更明确的查询和处理更容易。
-
矫枉过正?:在某些情况下,如果数据不是那么大,优化性能可能矫枉过正。
如何衡量绩效
关于如何衡量性能的一些线索?
- Slow query logs
- ANALYZE TABLE
- SHOW TABLE STATUS LIKE
-
EXPLAIN; Extended EXPLAIN Output Format; using explain; explain output
- SHOW WARNINGS
一些有趣的分析工具: