使用数据透视表关系提高大型数据集的性能(使用 Laravel)

Posted

技术标签:

【中文标题】使用数据透视表关系提高大型数据集的性能(使用 Laravel)【英文标题】:Increase performance on large dataset with pivot table relationships (Using Laravel) 【发布时间】:2020-08-05 04:58:18 【问题描述】:

寻求关于我是否可以改进我的数据库语句或者我是否应该开始缓存查询结果以提高性能的建议。

架构设置为Many-To-Many Polymorphic 关系。我有一个包含视频信息的Videos 表、一个包含所有类别的Category 表和一个包含数据透视信息的Categorizable 表。

VideosCategorizable 之间的比例约为 1:4。 (即每个视频至少有 4 个以上的类别)。

访问具有 40 行限制和 WITHOUT 偏移量的数据透视数据时的结果是:~1.2s+。 当偏移量 > 50,000 行时,添加偏移量会增加更多。

虽然 1.2 秒看起来很小,但这只是整个数据集的一小部分,最终包含大约 3000 万条视频记录(因此有大约 12+ 百万条可分类记录)。我担心每百万条记录会增加 1.2 秒。

数据库架构

视频表

-------------------------------------------------- ---------------------- 编号 |标题 |作者 |意见 |持续时间 |等等。 -------------------------------------------------- ---------------------- 1 |最大的词是什么? |字典 | 3432 | 600 | ... 2 | 2020 年年度视频综述 |优酷 | 165 |第945章... 3 |谷歌搜索引擎优化帮助 |谷歌 | 1401 | 287 | ... ↓ 101234 |如何煮意大利面 |优酷 | 9401 | 87 | ...

索引:

-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------- 表 |非唯一 |键名 | Seq_in_index |列名 |整理 |基数|子部分 |包装 |空 |索引类型 |评论 |索引评论 |可见 |表达 -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------- 视频 | 0 |初级 | 1 |编号 |一个 | 253057 |空 |空 | | BTREE | | |是 |空值 视频 | 1 | idx_videos_views | 1 |意见 |一个 | 102188 |空 |空 |是 | BTREE | | |是 |空值

分类表

-------------------------------------------------- ------------ 编号 |类别 ID |可分类 ID |可分类类型 -------------------------------------------------- ------------ 1 | 5 | 1 | '视频' 2 | 100 | 2 | '视频' 3 | 31 | 3 | '视频' ↓ 299052 | 65 | 101234 | '视频'

索引:

-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------------------- 表 |非唯一 |键名 | Seq_in_index |列名 |整理 |基数|子部分 |包装 |空 |索引类型 |评论 |索引评论 |可见 |表达 -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------------------- 分类 | 0 |初级 | 1 |编号 |一个 | 296745 |空 |空 | | BTREE | | |是 |空值 分类 | 1 | idx_category_id | 1 |类别 ID |一个 | 82 |空 |空 | | BTREE | | |是 |空值 分类 | 1 | idx_categorizable_id | 1 |分类ID |一个 | 104705 |空 |空 | | BTREE | | |是 |空值

类别表

-------------------- 编号 |姓名 -------------------- 1 |教育 2 |健康 3 |娱乐 ↓ 100 |消息

索引:

-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------------------- 表 |非唯一 |键名 | Seq_in_index |列名 |整理 |基数|子部分 |包装 |空 |索引类型 |评论 |索引评论 |可见 |表达 -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------------------------------------- 类别 | 0 |初级 | 1 |编号 |一个 | 100 |空 |空 | | BTREE | | |是 |空值

mysql

类型:InnoDB

Laravel 查询

Category::where('id', $cat)
  ->with(['videos' => function($query) 
    return $query->take(40)->orderby('views'); 
   ])
   ->get();

变成 MySQL 查询

SELECT `videos`.`id`, `views` 
FROM `videos` inner join `categorizables` 
ON `videos`.`id` = `categorizables`.`categorizable_id`
WHERE `categorizables`.`category_id` = 1 
ORDER BY `views` desc 
LIMIT 40 offset 0

性能结果

以下是 MySQL 的性能输出

-------------------------------------------------- -------- 舞台 |期间 -------------------------------------------------- -------- 阶段/sql/开始 | 0.000068 stage/sql/Executing 交易钩子开始。 | 0.000000 阶段/sql/开始 | 0.000003 阶段/sql/检查权限 | 0.000001 阶段/sql/检查权限 | 0.000001 stage/sql/开表| 0.000038 阶段/sql/init | 0.000003 stage/sql/系统锁 | 0.000005 阶段/sql/优化 | 0.000007 阶段/sql/统计 | 0.005628 阶段/sql/准备 | 0.000008 stage/sql/创建 tmp 表 | 0.000033 阶段/sql/执行 | 1.273442 阶段/sql/结束 | 0.000001 阶段/sql/查询结束 | 0.000001 阶段/sql/等待处理程序提交 | 0.000008 阶段/sql/删除 tmp 表 | 0.000003 阶段/sql/关闭表| 0.000006 stage/sql/释放项目 | 0.000080 阶段/sql/清理| 0.000000

具体来说:

stage/sql/executing | 1.273442

查询费用

---------------------------------- 变量名 |价值 ---------------------------------- Last_query_cost | 107258.575124

编辑:

解释查询

带排序:

-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ----------------- 编号 |选择类型 |表|隔断 |类型 |可能的键 |关键 | key_len |参考 |行 |过滤 |额外的 -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ----------------- 1 |简单 |分类 |空 |参考 | idx_category_id,idx_categorizable_id | idx_category_id | 4 |常量 | 51210 | 100.00 |使用临时的;使用文件排序 1 |简单 |视频 |空 | eq_ref |初级 |初级 | 4 | dev_db.categorizables.categorizable_id | 1 | 100.00 |使用索引

没有排序:

-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ---------------------------------- 编号 |选择类型 |表|隔断 |类型 |可能的键 |关键 | key_len |参考 |行 |过滤 |额外的 -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ---------------------------------- 1 |简单 |视频 |空 |索引 |空 |初级 | 4 |空 | 40 | 100.00 |向后索引扫描;使用索引

【问题讨论】:

所以,现在我们看到排序会减慢查询速度,我们可以将其分解为几个查询。 (现在尽量避免加入)。我看到的问题是 category_id 的索引基数是 82(对于你的大表来说太小了) 不幸的是,我不能忽略 INNER JOIN,因为它是应用程序的重要组成部分 - 能够通过类别对视频进行排序是必不可少的。我会将索引基数更新为更高的值,但这是通过 MySQL 工作台自动设置的(不确定是否正确)。我很难理解为什么在 300k 可分类行上需要这么长时间。 您无法更新它。一个黑客可以提供帮助。如果您知道您将获得的所有行的“视图”多于 N,您可以尝试将其添加到查询中 通过排序会影响 51210 行,目标是降低此值 views 在哪个表中??它有很大的不同。请提供SHOW CREATE TABLE 【参考方案1】:

让我带你了解最坏的情况:

SELECT  v.`id`, v.`views`
    FROM  `videos` AS v
    inner join  `categorizables` AS c  ON v.`id` = c.`categorizable_id`
    WHERE  c.`category_id` = 1
    ORDER BY  v.`views` desc
    LIMIT  40 offset 50000 

流程是这样的:

    categorizables 中查找带有category_id = 1 的所有行。这可能会或可能不会使用索引:INDEX(category_id, categorizable_id) 可能会有所帮助。 对于这些行中的每一行,请访问videos 以获取viewsid。假设idPRIMARY KEY,我没有添加推荐。 将所有内容收集到一个临时表中。 (大概超过 50K 行?) 对该表进行排序。 通读排序表,跳过 50000 多行。 交付 40 行并退出。

我希望很明显,删除排序或删除偏移量或(等)将导致简化的执行计划,从而更快。

你说有一个多对多的关系?是categorizables 吗?它是否遵循此处的性能提示:http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table?

【讨论】:

感谢您的深入回复。是的,categorizables 是数据透视表。我相信我的设置类似于链接的文章。是的,删除排序会删除长时间的执行,但如果不是这样,那么如何对大型数据集进行排序? @l3fty - 大的OFFSET 也是呆滞中的一个问题。 @l3fty - 当可以使用索引时,可以避免排序。但是这种优化的方式有多个。例如,将category_id 移动到videos 将使INDEX(category_id, views) 避免排序。 (但随后巨大的偏移量成为阻碍。)【参考方案2】:

您可以查看此Laravel Debugbar 以了解您正在执行多少重复查询。

接下来,您可以在获取数据时立即加载关系,例如:

索引控制器

$videos = Videos::with('categories')->get();
return $videos;

你也可以为 Laravel 使用缓存,例如:

$videos = \Cache::rememberForever('key', function() 
  return Video::with('categories')->get();
);

return $videos;

【讨论】:

感谢您的回复,但我对Category::with('videos') 的调用与Videos::with('categories') 正好相反。这是一个 INNER JOIN 查询。如果我无法减少此查询时间,我还计划使用缓存,尽管对于较大的数据集,我担心即使初始查询也会很慢。 你也可以做分页。或查看更多?

以上是关于使用数据透视表关系提高大型数据集的性能(使用 Laravel)的主要内容,如果未能解决你的问题,请参考以下文章

Firebase 与大型数据集的性能

如何使用精确的数据透视表关系

在 Pandas 中处理大型数据透视表

使用数据透视表数据获取关系表的信息

数据透视表:由于临时表而处理大型 CommandText

laravel 使用具有多对多关系数据透视表的策略