質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

データベース

データベースとは、データの集合体を指します。また、そのデータの集合体の共用を可能にするシステムの意味を含めます

最適化

最適化とはメソッドやデザインの最適な処理方法を選択することです。パフォーマンスの向上を目指す為に行われます。プログラミングにおける最適化は、アルゴリズムのスピードアップや、要求されるリソースを減らすことなどを指します。

Q&A

2回答

724閲覧

MySQL 各グループの中から上位n個のアイテム取得が遅いので効率化したい

mipopon

総合スコア38

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

データベース

データベースとは、データの集合体を指します。また、そのデータの集合体の共用を可能にするシステムの意味を含めます

最適化

最適化とはメソッドやデザインの最適な処理方法を選択することです。パフォーマンスの向上を目指す為に行われます。プログラミングにおける最適化は、アルゴリズムのスピードアップや、要求されるリソースを減らすことなどを指します。

0グッド

0クリップ

投稿2020/09/03 22:50

SQL

1create table `users` ( 2`user_id` int auto_increment primary key, 3`active` tinyint 4)engine=innoDB; 5 6create table `articles` ( 7`article_id` int auto_increment primary key, 8`user_id` int, 9`created_at` datetime 10)engine=innoDB; 11 12create table `article_metadata_ref` ( 13`metadata_id` int, 14`article_id` int, 15Primary Key(`metadata_id`, `article_id`) 16);

説明用に最小サンプルを作成しました。
PHP PDO からデータベースに繋いでいます。

user テーブルのactive フラグか articles テーブルの created_at の日付をみて活動していると判断できるユーザーのアイテムn個に紐付いているmetadata_id とそのcount(*) をユーザー毎に取得するのが目的です。

自分でも作成はしたのですがかなり遅いので最適化して少しでも早くしたいと思いますので解決の糸口でも頂ければ幸いです。

試した事

まずは正直にでユーザーIDのリストを取得してから

SQL

1select `user_id` from users where `active` = 1;

各ユーザーのarticle_id をn個 取得してref table とjoin

SQL

1select `metadata_id`, count(*) from 2 (select `article_id` 3 from articles where `user_id` = :user_id order by created_at desc limit :n 4 ) as articles 5 inner join article_metadata_ref as ref on articles.`article_id` = ref.`article_id` 6group by `metadata_id` 7 8結果: 9+-------------+----------+ 10| metadata_id | count(*) | 11+-------------+----------+ 12| 1 | 1 | 13| 2 | 1 | 14+-------------+----------+ 152 rows in set (0.00 sec)

とこの様な形でユーザーのmetadata_idと使用頻度を取得しました。

上記のクエリで取得したmetadata_idを他のユーザーと比較したい場合はユーザーの数だけクエリを発行する必要があってユーザー数が多いと大変時間がかかります。

なので他のユーザーのmetadata_id を同時に取得しようと思って下記のクエリを考えてみました。

SQL

1select `metadata_id`, count(*) from 2 (select `article_id`, `user_id` 3 from articles where `user_id` IN (:uid1, :uid2, ...) order by created_at desc limit :(n*ユーザー数) 4 ) as articles 5 inner join article_metadata_ref as ref on articles.`article_id` = ref.`article_id` 6group by `metadata_id`,articles.`user_id` 7 8結果: 9+---------+-------------+----------+ 10| user_id | metadata_id | count(*) | 11+---------+-------------+----------+ 12| 1 | 1 | 1 | 13| 2 | 1 | 1 | 14| 1 | 2 | 1 | 15| 2 | 2 | 1 | 16| 1 | 3 | 1 | 17| 1 | 4 | 1 | 18| 1 | 5 | 1 | 19+---------+-------------+----------+ 207 rows in set (0.00 sec)

ただこちらのクエリだと limit n * ユーザー数ですが 特定ユーザーのarticle数が均等では無いのでデータに偏りが出る可能性がありますので使えないと思いました。

その他にrow_number() を使って取得出来ると読みましたがmysql のバージョンが5.6 なので使えないのと変数を使って実現してもユーザーの全てのarticle のレコードにrow_number を付けてからwhere でフィルタするので結果遅くなりました。

リアルタイムでmetadata_id によるユーザー間の類似度のスコアリングはスピード的に無理そうなのでcronで先に計算したテーブルを用意しようと思いましたがそれでも数万ユーザー分の計算だと大変遅いので良い方法は無いでしょうか。

直接の解決策ではなくてもアドバイスなどコメントご教示頂ければ幸いです。
またこの様なレコメンドの実装に関してもご意見頂ければと思います。調べるとhashを使ったの高速の類似度の近似値の求め方などが出てきますがそもそもデータを用意するのが遅すぎてその様な計算の高速化以前の問題で止まっています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2020/09/03 23:49

MySQLのバージョンを追記してください。
sazi

2020/09/04 05:25 編集

何に対してlimitしたいのかよく分かりません。 user_id毎にn個のarticle_idですか? それだと、件数はn以下にしかなりませんけど、それでいいのでしょうか?
sazi

2020/09/04 05:29

> articles テーブルの created_at の日付をみて活動していると判断 どんな条件ですか?
guest

回答2

0

ちょっと重さの感覚がわからないですが、内部結合で取得ではダメですかね。

SQL

1select 2 users.user_id, 3 ref.metadata_id, 4 count(*) 5from article_metadata_ref ref 6inner join articles art on 7art.article_id = ref.article_id 8inner join users on 9users.user_id = art.user_id 10where users.active = 1 11group by ref.metadata_id, users.user_id

投稿2020/09/04 01:08

yureighost

総合スコア2183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

以下の2つのテーブルにインデックスを追加しては?
articles.user_id
article_metadata_ref.article_id

インデックスの追加前後の
MySQL SQL実行計画の疑問解決には「とりあえずEXPLAIN」しようを質問に追記しては?

投稿2020/09/04 01:05

Orlofsky

総合スコア16417

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問