2023年8月7日13:59:31
因为最近开发自己的一些常用系统,所以为了自由度较高一点,经常分类都是无限层级,所以递归用的比较多,但是发现当分类大于三层,数据1万以上递归就会很慢,所以一直在寻求优化算法,使用使用chagpt优化的算法,基本无法使用,后续想到用php原生函数来使用,结果性能飙升
数据库结构:
CREATE TABLE `admin_permission` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
`is_delete` tinyint(1) NOT NULL DEFAULT '10' COMMENT '10默认99删除',
`parent_permission_id` int unsigned NOT NULL DEFAULT '0' COMMENT '父ID 0是顶级',
`permission_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '控制名称',
`permission_url` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '前台控制器URL',
`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '组件名称',
`backstage_permission_url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '后台权限URL',
`tag` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '标签,标志',
`is_menu` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '作为菜单显示,1是,2不是',
`small_icon_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'pc端图标',
`redirect` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '跳转页面',
`component` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '组件类型',
`keep_alive` tinyint NOT NULL DEFAULT '1' COMMENT '是否keep_alive',
`hidden` tinyint(1) DEFAULT '10' COMMENT '菜单是否显示10显示20不显示',
PRIMARY KEY (`id`),
KEY `parent_permission_id` (`parent_permission_id`)
) ENGINE=InnoDB AUTO_INCREMENT=14384 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员权限表';
生产测试数据:,随便自己插入几条数据,然后使用下面的方法在第三层生成测试数据
$list = AdminPermission::orderBy('id', 'asc')->get()->toArray();
$array = self::treeMenu($list, 0, 'parent_permission_id');
// pp($array);
// 制造10000+的测试数据,测试算法姓名
foreach ($array as $k => &$v) {
if (!empty($v['children'])) {
foreach ($v['children'] as $kk => &$vv) {
if (!empty($vv['children'])) {
foreach ($vv['children'] as $kkk => &$vvv) {
unset($vvv['label']);
unset($vvv['id']);
for ($i = 1; $i <= 100; $i++) {
DB::table('admin_permission')->insert($vvv);
}
}
}
}
}
}
php实现
//递归数据
public static function treeMenu(array $menu = [], int $parent = 0, string $parentKey = 'parent_id')
{
$tree = array();
foreach ($menu as $v) {
if ($v[$parentKey] == $parent) {
$v['children'] = self::treeMenu($menu, $v['id'], $parentKey);
if (empty($v['children'])) {
unset($v['children']);
}
$tree[] = $v;
}
}
return $tree;
}
使用php原生函数实现的,默认三层搜索
$tree = array_filter($list, function ($permission) use ($parent_id, $parentKey) {
return $permission[$parentKey] == $parent_id;
});
if (!empty($tree)) {
foreach ($tree as &$v) {
$parent_id = $v['id'];
$v['children'] = array_filter($list, function ($permission) use ($parent_id, $parentKey) {
return $permission[$parentKey] == $parent_id;
});
if (!empty($v['children'])) {
foreach ($v['children'] as &$vv) {
$parent_id = $vv['id'];
$vv['children'] = array_filter($list, function ($permission) use ($parent_id, $parentKey) {
return $permission[$parentKey] == $parent_id;
});
// if (!empty($vv['children'])) {
// foreach ($vv['children'] as &$vvv) {
//
// $parent_id = $vvv['id'];
// $vvv['children'] = array_filter($list, function ($permission) use ($parent_id, $parentKey) {
// return $permission[$parentKey] == $parent_id;
// });
// }
// }
}
}
}
}
测试:
1,php实现方法测试:
第一次:
待递归总数--:14350
开始时间:2023-08-08 10:28:22
结束时间:2023-08-08 10:28:28
第二次:
待递归总数--:14350
开始时间:2023-08-08 10:29:04
结束时间:2023-08-08 10:29:09
2,php原生实现方法测试:
第一次:
待递归总数--:14350
开始时间:2023-08-08 10:30:46
结束时间:2023-08-08 10:30:46
第二次:
待递归总数--:14350
开始时间:2023-08-08 10:31:00
结束时间:2023-08-08 10:31:01
完整代码:
public function index(Request $request)
{
$list = AdminPermission::orderBy('id', 'asc')->get()->toArray();
$array = self::treeMenu($list, 0, 'parent_permission_id');
// pp($array);
// 制造10000+的测试数据,测试算法姓名
foreach ($array as $k => &$v) {
if (!empty($v['children'])) {
foreach ($v['children'] as $kk => &$vv) {
if (!empty($vv['children'])) {
foreach ($vv['children'] as $kkk => &$vvv) {
unset($vvv['label']);
unset($vvv['id']);
for ($i = 1; $i <= 100; $i++) {
DB::table('admin_permission')->insert($vvv);
}
}
}
}
}
}
//die;
$count = AdminPermission::orderBy('id', 'asc')->get()->count();
p('待递归总数--:' . $count);
$list = AdminPermission::orderBy('id', 'asc')->get(['id', 'parent_permission_id', 'permission_name'])->toArray();
//php代码实现递归
p('开始时间:' . date('Y-m-d H:i:s'));
$rr = self::treeMenu($list, 0, 'parent_permission_id');
p('结束时间:' . date('Y-m-d H:i:s'));
//php原生函数实现递归
p('开始时间:' . date('Y-m-d H:i:s'));
$parent_id = 0;
$parentKey = 'parent_permission_id';
$tree = array_filter($list, function ($permission) use ($parent_id, $parentKey) {
return $permission[$parentKey] == $parent_id;
});
if (!empty($tree)) {
foreach ($tree as &$v) {
$parent_id = $v['id'];
$v['children'] = array_filter($list, function ($permission) use ($parent_id, $parentKey) {
return $permission[$parentKey] == $parent_id;
});
if (!empty($v['children'])) {
foreach ($v['children'] as &$vv) {
$parent_id = $vv['id'];
$vv['children'] = array_filter($list, function ($permission) use ($parent_id, $parentKey) {
return $permission[$parentKey] == $parent_id;
});
// if (!empty($vv['children'])) {
// foreach ($vv['children'] as &$vvv) {
//
// $parent_id = $vvv['id'];
// $vvv['children'] = array_filter($list, function ($permission) use ($parent_id, $parentKey) {
// return $permission[$parentKey] == $parent_id;
// });
// }
// }
}
}
}
}
p('结束时间:' . date('Y-m-d H:i:s'));
pp($tree);
}
//递归数据
public static function treeMenu(array $menu = [], int $parent = 0, string $parentKey = 'parent_id')
{
$tree = array();
foreach ($menu as $v) {
if ($v[$parentKey] == $parent) {
$v['children'] = self::treeMenu($menu, $v['id'], $parentKey);
if (empty($v['children'])) {
unset($v['children']);
}
$tree[] = $v;
}
}
return $tree;
}
总结
1,如果使用php实现某些算法性能不好的时候,一定要使用php原生函数试试
2,如果使用php原生函数也无法解决性能问题,建议使用不同请求模式来解决了,比如实现海量无线分类使用异步请求,一级请求之后,如果要打开某个二级就异步请求数据来展示,延迟加载和分页查询:在处理菜单数据时,不立即加载所有子菜单项,而是在需要访问子菜单时再进行加载。可以使用分页查询的方式,每次只查询一部分数据,减少内存使用和查询时间。
3,使用chatgpt来优化算法,不太靠谱