簡體   English   中英

如何提高過濾系統中 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.idgenres.id分別是videosgenres表的主要索引。 外鍵設置在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();
}
  • $type_code 將僅等於 0,1,2,3
  • 預覽和標題與視頻是一對一的關系

我的問題是:

  • 有沒有辦法讓這個查詢更好但維護過濾器 function?
  • 我在網上查了一下,有人說我們應該使用像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

更新 2

我添加了列、索引和關系截圖。 很抱歉,我無法提供 laravel 遷移文件,因為我在學習遷移之前在 phpmyadmin 中創建了這些表。 但似乎所有必需的關系都是根據多對多文檔添加的。

actors 在此處輸入圖像描述

videos 在此處輸入圖像描述

actor_vidio 在此處輸入圖像描述

previews 在此處輸入圖像描述

titles 在此處輸入圖像描述

再次抱歉讓問題變得混亂。

更新 3

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

在此處輸入圖像描述

更新 4

我添加了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)

videospreviews中像這樣

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM