![](/img/trans.png)
[英]How to create a filter by category in MANY TO MANY relationship in Laravel?
[英]How to improve query speed on Laravel many to many relationship in a filter system?
我用 laravel 8 建立了一個網站。服務器是 6 核 CPU / 6 GB Ram VPS。 服務器是 Linux CentOS 與 nginx 和 Z81C3B080DAD537DE7EZ10E0987A4BF52。
在高峰期,大約有500人同時在線訪問。 CPU 在高峰期達到 100%,在當時的 rest 中達到 80% 以上。
我檢查了使用情況,發現大部分資源被 mysql 使用。 然后我找到了一些慢查詢,我認為這種多對多關系查詢是主要原因之一。
有一個視頻 model 和類型 model 具有多對多關系設置。 在視頻表中,大約有 800,000 行。 流派表中有 700 多個流派,流派視頻表中有genre_video
個關系。 videos.id
和genres.id
分別是videos
和genres
表的主要索引。 外鍵設置在genre_video
視頻 model
class Video extends Model
{
use HasFactory;
public function genres()
{
return $this->belongsToMany(Genre::class);
}
}
類型 Model
class Genre extends Model
{
use HasFactory;
public $timestamps = false;
public function videos()
{
return $this->belongsToMany(Video::class);
}
}
表
videos
id video_info1 video_info2 type_code
1 somethining somethining 1
2 somethining somethining 1
3 somethining somethining 1
genres
id genre_name
1 G1
2 G2
3 G3
4 G4
5 G5
genre_video
genre_id video_id
1 1
1 3
1 5
2 1
2 3
previews (one-to-one with video)
id image
1 aaa.jpg
2 bbb.jpg
3 ccc.jpg
titles (one-to-one with video)
id title
1 aaa
2 bbb
3 ccc
過濾器 function
我的網站上有一個流派列表。 當訪問者點擊流派時,它會更改 url。
例如:Gerne 列表: G1 G2 G3 G4 G5
當訪客點擊 G1 時,url 變為/?c=1
然后訪問者點擊 G3,url 變為/?c=1,3
然后訪問者點擊 G5,url 變為/?c=1,3,5
function 將獲取所有選定的流派 id 作為數組$cArr
。 然后我使用whereHas
循環遍歷數組以查找匹配類型 1、3、5 的所有視頻。 隨着訪問者在過濾器中添加更多類型,他們可以准確找到他們想要的內容。 即示例中 id = 1 的視頻。 但是這個查詢大約需要 20-50 秒。
if($request->c){
$c = $request->c;
$cArr = explode(',',$c);
$data = Video::where('type_code',$type_code)
->whereHas('genres',function ($query) use($cArr) {
$query->whereIn('genres.genre_id', $cArr);
}, '=', count($cArr))
->join('previews','previews.code','=','videos.code')
->join('titles','titles.code','=','videos.code')
->orderBy('publish_date', 'DESC')
->limit(400)->get();
}
我的問題是:
sphinex
這樣的索引引擎。 I don't know if it is compatible with my settings linux + centOS 7 + nginx + mysql 8 + laravel 8
. 關於使用索引引擎的任何建議?感謝您花時間閱讀我的問題。 以下是實際生成的查詢的一些示例。 時間已經處於最佳速度,因為它在一天中的流量最低。
第一個是當訪問者點擊 G2 和 G13 時。 他將看到 400 個類型為 G2 和 G13 的視頻。
select * from `videos`
inner join `previews` on `previews`.`code` = `videos`.`code`
inner join `titles` on `titles`.`code` = `videos`.`code`
where `type_code` = 0 and (
select count(*)
from `genres`
inner join `genre_video` on `genres`.`id` = `genre_video`.`genre_id`
where `videos`.`id` = `genre_video`.`video_id`
and `genres`.`genre_id` in ('2', '13')
) = 2 order by `publish_date` desc limit 400
Query took 13.58s
第二個是當訪問者點擊 G2、G13 和 G18 時。 他甚至會看到所有這些類型的 400 個經過精確過濾的視頻
select * from `videos`
inner join `previews` on `previews`.`code` = `videos`.`code`
inner join `titles` on `titles`.`code` = `videos`.`code`
where `type_code` = 0 and (
select count(*) from `genres`
inner join `genre_video` on `genres`.`id` = `genre_video`.`genre_id`
where `videos`.`id` = `genre_video`.`video_id`
and `genres`.`genre_id` in ('2', '13', '18')
) = 3 order by `publish_date` desc limit 400
Query took 14.04s
我添加了列、索引和關系截圖。 很抱歉,我無法提供 laravel 遷移文件,因為我在學習遷移之前在 phpmyadmin 中創建了這些表。 但似乎所有必需的關系都是根據多對多文檔添加的。
再次抱歉讓問題變得混亂。
EXPLAIN select * from `videos`
inner join `previews` on `previews`.`code` = `videos`.`code`
inner join `titles` on `titles`.`code` = `videos`.`code`
where `type_code` = 0 and (
select count(*) from `genres`
inner join `genre_video` on `genres`.`id` = `genre_video`.`genre_id`
where `videos`.`id` = `genre_video`.`video_id`
and `genres`.`genre_id` in ('2', '13', '18')
) = 3 order by `publish_date` desc limit 400
EXPLAIN select * from `videos`
inner join `previews` on `previews`.`code` = `videos`.`code`
inner join `titles` on `titles`.`code` = `videos`.`code`
where `type_code` = 0 and (
select count(*) from `genres`
inner join `genre_video` on `genres`.`id` = `genre_video`.`genre_id`
where `videos`.`id` = `genre_video`.`video_id`
and `genres`.`genre_id` in ('2', '13', '18')
) = 3 order by `publish_date` desc limit 400
我添加了SHOW CREATE TABLE
actors
actors
CREATE TABLE `actors` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`actor_id` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs NOT NULL,
`actor_type` int unsigned NOT NULL DEFAULT '0',
`actor_img` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`actor_sex` int DEFAULT '2',
`actor_cn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`actor_tw` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`actor_en` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`actor_ja` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`actor_ko` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `actor_id` (`actor_id`),
KEY `actor_sex` (`actor_sex`)
) ENGINE=InnoDB AUTO_INCREMENT=89588 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
videos
CREATE TABLE `videos` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`type_code` int NOT NULL,
`code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`publish_date` date NOT NULL,
`duration` int NOT NULL,
`download` int NOT NULL,
`sub` int NOT NULL,
`online` int NOT NULL DEFAULT '0',
`leak` int NOT NULL DEFAULT '0',
`javdb_url_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`is_single_actor` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`),
KEY `type_code` (`type_code`),
FULLTEXT KEY `code_fulltext` (`code`)
) ENGINE=InnoDB AUTO_INCREMENT=458527 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
actor_video
CREATE TABLE `actor_video` (
`video_id` int unsigned NOT NULL,
`actor_id` int unsigned NOT NULL,
PRIMARY KEY (`video_id`,`actor_id`),
KEY `actor_video_actor_id_foreign` (`actor_id`) USING BTREE,
KEY `actor_video_video_id_foreign` (`video_id`) USING BTREE,
KEY `actor_id` (`actor_id`),
KEY `video_id` (`video_id`),
CONSTRAINT `actress_video_actress_id_foreign` FOREIGN KEY (`actor_id`) REFERENCES `actors` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `actress_video_video_id_foreign` FOREIGN KEY (`video_id`) REFERENCES `videos` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
previews
CREATE TABLE `previews` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`video_preview` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image_pl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image_ps` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image_preview_s` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image_preview` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `code` (`code`)
) ENGINE=InnoDB AUTO_INCREMENT=458096 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
titles
CREATE TABLE `titles` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`title_cn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`title_tw` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`title_en` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`title_ja` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`title_ko` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `code` (`code`),
FULLTEXT KEY `title_index` (`title_ja`,`title_en`) /*!50100 WITH PARSER `ngram` */ ,
CONSTRAINT `title_video_fk` FOREIGN KEY (`code`) REFERENCES `videos` (`code`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE=InnoDB AUTO_INCREMENT=458101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
genres
CREATE TABLE `genres` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`genre_id` int NOT NULL,
`genre_type` int NOT NULL DEFAULT '0',
`genre_cn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`genre_tw` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`genre_en` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`genre_ja` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`genre_ko` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`type_code` int NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `genre_id` (`genre_id`),
FULLTEXT KEY `genre_cn` (`genre_cn`,`genre_tw`,`genre_en`,`genre_ja`)
) ENGINE=InnoDB AUTO_INCREMENT=1536 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
genre_video
CREATE TABLE `genre_video` (
`genre_id` int unsigned NOT NULL,
`video_id` int unsigned NOT NULL,
KEY `genre_video_genre_id_foreign` (`genre_id`),
KEY `genre_video_video_id_foreign` (`video_id`),
KEY `genre_id` (`genre_id`),
CONSTRAINT `genre_video_genre_id_foreign` FOREIGN KEY (`genre_id`) REFERENCES `genres` (`id`) ON DELETE CASCADE,
CONSTRAINT `genre_video_video_id_foreign` FOREIGN KEY (`video_id`) REFERENCES `videos` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Many:to:many 表的索引往往很差,導致大量額外的 CPU。 這顯示了最佳模式(無 auto_inc)和索引(2 個復合索引):
http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table
首先,您的 JOIN 的通用鍵是code
。
它在titles
中是這樣定義的
code varchar(50)
在videos
和previews
中像這樣
code varchar(255)
這對ON
條件下的性能不利。 定義所有三個code
列完全相同。
下一期:讓我們告別依賴子查詢。
我們可以寫
select genre_video.video_id
from genres
join genre_video ON genres.id = genre_video.id
join videos on genre_video.video_id = videos.id
where genres.genre_id IN (2, 13, 18)
and videos.type_code = 0
group by genre_video.video_id
having COUNT(*) = 3
獲取與三種類型匹配的video_id
值。
然后我們將子查詢加入另一個子查詢。 這order by... limit 400
。
select code
from (
select genre_video.video_id
from genres
join genre_video ON genres.id = genre_video.id
join videos on genre_video.video_id = videos.id
where genres.genre_id IN (2, 13, 18)
and videos.type_code = 0
group by genre_video.video_id
having COUNT(*) = 3
) matches
join videos ON matches.video_id = videos.id
order by publish_date desc
limit 400
只有這樣我們才能加入各種表並執行select *
select *
from (
select code
from (
select genre_video.video_id
from genres
join genre_video ON genres.id = genre_video.id
join videos on genre_video.video_id = videos.id
where genres.genre_id IN (2, 13, 18)
and videos.type_code = 0
group by genre_video.video_id
having COUNT(*) = 3
) matches
join videos ON matches.video_id = videos.id
order by publish_date desc
limit 400
) chosen
join videos on chosen.code = videos.code
join titles on chosen.code = titles.code
order by videos.publish_date;
這應該有很大幫助。
抱歉,我不知道如何在 Laravel 中編碼。
你沒有向我們展示genres
,但它應該有這兩個索引才能讓這種東西有效地工作。 它不需要任何單個列上的索引。
PRIMARY KEY (video_id, genre_id)
INDEX (genre_id, video_id)
(順便說一下,這個關於索引的建議也適用於actor_video
。)
像這樣棘手的問題是真正應用的標志。 隨着應用程序的增長,您應該監控性能。 您可能需要其他索引,或者需要重構其他查詢。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.