web-dev-qa-db-ja.com

JOIN、WHERE、ORDER BYの組み合わせ(MySQL)のパフォーマンスが低い

目標:特定のユーザーに関連するpostsから最新の10行を選択します。

2つのテーブルがあります:posts(〜5,000,000行)とrelations(〜8,000行)。

posts列:

--------------------------------------------------------------------------------------------
|  id (int)  |  source_id (int)  |  title (varchar)  |  content (longtext)  |  date (int)  |
--------------------------------------------------------------------------------------------

relations列:

----------------------------------------------------
|  id (int)  |  source_id (int)  |  user_id (int)  |
----------------------------------------------------

特定のユーザーに関連するpostsから最新の10行を取得してJOINを使用してみました

SELECT      p.id, p.title, p.content, r.id AS rid
FROM        posts AS p
JOIN        relations AS r
ON          r.source_id = p.source_id
WHERE       r.user_id = 1
ORDER BY    p.date DESC
LIMIT       10

ただし、実行に〜30秒かかります(SDDホスティング!)。単一の列と複数の列のインデックスを含め、両方のテーブルで多くのインデックスの組み合わせを試しましたが、いずれも実行時間に影響を与えませんでした。 選択をスピードアップする方法はありますか?

ために user_id=1〜1,000ありますsource_idpostsの〜450,000行。

SHOW CREATE TABLE... 結果:

CREATE TABLE `relations` (
 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 `user_id` bigint(20) unsigned NOT NULL,
 `source_id` bigint(20) unsigned DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `user_id` (`user_id`),
 KEY `source_id` (`source_id`),
 KEY `source_id_2` (`source_id`,`user_id`),
) ENGINE=InnoDB AUTO_INCREMENT=7692 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `posts` (
 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 `source_id` bigint(20) unsigned NOT NULL,
 `title` varchar(512) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
 `content` longtext CHARACTER SET utf8 COLLATE utf8_unicode_ci,
 `date` int(10) unsigned DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `source_id` (`source_id`),
 KEY `date` (`date`),
 KEY `date_2` (`date`,`source_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4867283 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

EXPLAIN結果:

EXPLAIN results

3
Osvaldas

MySQLはデータセットを使用して、投稿から450,000レコード(一致する各source_idから1000個の小さなチャンクで)を取得し、それを並べ替えて、上位10件を返す必要があります。これはコストのかかる作業です。

ストアドプロシージャを使用して結果を蓄積し、たとえば毎日または毎週、少なくとも10個のレコードを取得するまでループし、次に最新の10個を返すようにすることができます。 _(date, source_id)_によるpostsのインデックスが必要です。最近アクティブなユーザーの場合はすぐに戻りますが、最近の投稿がないユーザーの場合は時間がかかります。次のようなもの:

_DELIMITER ;;
CREATE DEFINER=CURRENT_USER PROCEDURE stored_procedure_name(u_id int)
BEGIN
  DECLARE fd DATE;
  DECLARE d DATE;

  SELECT MIN(date), MAX(date) INTO fd, d FROM posts;

  CREATE TEMPORARY TABLE last_posts (id int);

  WHILE d > fd AND (SELECT COUNT(*) FROM last_posts) < 10 DO
    INSERT INTO last_posts (id)
    SELECT p.id
    FROM relations AS r
    JOIN posts AS p ON (p.source_id = r.source_id AND
                        p.date > date_sub(d, interval 7 day) AND p.date <= d)
    WHERE r.user_id = u_id
    ORDER BY p.date DESC
    LIMIT 10;

    SET d = date_sub(d, interval 7 day);
  END WHILE;

  SELECT p.id, p.title, p.content, r.id AS rid
  FROM posts p
  JOIN relations AS r ON (r.source_id = p.source_id)
  WHERE p.id IN (SELECT * FROM last_posts)
  ORDER BY p.date DESC
  LIMIT 10;

  DROP TABLE last_posts;
END;;
DELIMITER ;
_

間隔で遊んで、1日に減らすことができます(より高速なクエリでより多くのサイクル)。 posts (date)にインデックスがあることを確認してください。

このmightは、「遅延評価」により、実行が速くなります。いくつかの大きな列をフェッチする必要があるが、どの10が望ましいかを決定する前に数千の行を調べる必要があることに注意してください。必要なすべての列を収集する代わりに、PRIMARY KEYs、次にpostsに10回だけアクセスして、かさばる列を取得します。かさばるカラムはInnoDBの個別のブロックにオフレコで格納されることに注意してください。

(最初のテーブルを最初にリストするためだけに、@ Nicholasの公式から始めます。)

SELECT  p.id, p.title, p.content, rid
    FROM  
      ( SELECT  p.id AS pid, r.id AS rid
            FROM  relations AS r
            JOIN  posts AS p  ON r.source_id = p.source_id
            WHERE  r.user_id = 1
            ORDER BY  p.date DESC
            LIMIT  10 
      ) AS x
    JOIN  posts p  ON p.id = x.pid;

必要なインデックス。列の順序は重要です。

relations: INDEX(user_id, source_id, id) -- "covering"
posts:  INDEX(source_id, date, id) -- for the subquery; "covering"
posts:  PRIMARY KEY(id) -- for the outer query; already exists

サイドノート:

  • テーブルのデフォルトはutf8mb4ですが、列はutf8のみであることをご存知ですか?おそらく、utf8mb4への変換の不完全な試みですか? (必要なALTER TABLE ... CONVERT TO ...。)
  • BIGINTはやり過ぎです。
  • relationsが多対多のマッピングにすぎない場合、おそらく双方向のインデックスが必要です。 idの必要性を説明できますか? 推奨
2
Rick James