4

このクエリの最適化について助けが必要です。

  SELECT messages.*
   FROM messages
   INNER JOIN subscription ON subscription.entity_id = messages.entity_id
   WHERE subscription.user_id = 1
   ORDER BY messages.timestamp DESC 
   LIMIT 50

制限がない場合、このクエリは 20 万行を返し、実行に約 1.3 ~ 2 秒かかります。問題は order by 句にあるようです。これがないと、クエリに .0005 秒かかります。

Indexes:
    ( subscription.user_id, subscription.entity_id )
    ( subscription.entity_id )
    ( messages.timestamp )
    ( messages.entity_id, messages.timestamp )

クエリを次のように変更することで、パフォーマンスを向上させることができました。

SELECT messages.* FROM messages
INNER JOIN subscription ON subscription.entity_id = messages.entity_id 
INNER JOIN ( 
   SELECT message_id FROM messages ORDER BY timestamp DESC
) as temp on temp.messsage_id = messages.message_id
WHERE subscription.user_id = 1 LIMIT 50

これは 0.12 秒で実行されます。非常に素晴らしい改善ですが、改善できるかどうか知りたいです。どうにかして2番目の内部結合をフィルタリングできれば、物事はより速くなるようです。

ありがとう。

スキーマ:

   messages 
      message_id, entity_id, message, timestamp

   subscription
      user_id, entity_id

アップデート

Raymond Nijland's Answer は私の最初の問題を解決しますが、別の問題が発生しました

 SELECT messages.*
   FROM messages
   STRAIGHT_JOIN subscription ON subscription.entity_id = messages.entity_id
   WHERE subscription.user_id = 1
   ORDER BY messages.timestamp DESC 
   LIMIT 50

次の 2 つの場合、直接結合は非効率的です。

  1. サブスクリプション テーブルに user_id エントリがありません

  2. メッセージ テーブルには関連するエントリがほとんどありません

これを修正する方法について何か提案はありますか? クエリの観点からではない場合、アプリケーションの観点からですか?

アップデート

情報を説明

リミット50

| id | select_type | table             | type   | possible_keys                           | key           | key_len | ref                                    | rows | Extra       |
|  1 | SIMPLE      | messages          | index  | idx_timestamp                           | idx_timestamp | 4       | NULL                                   |   50 |             |
|  1 | SIMPLE      | subscription      | eq_ref | PRIMARY,entity_id,user_id               | PRIMARY       | 16      | const, messages.entity_id              |    1 | Using index |

制限なし

| id | select_type | table             | type   | possible_keys                           | key           | key_len | ref                                    |   rows   | Extra         |
|  1 | SIMPLE      | messages          | ALL    | entity_id_2,entity_id                   | NULL          | NULL    | NUL                                    |   255069 | Using filesort|
|  1 | SIMPLE      | subscription      | eq_ref | PRIMARY,entity_id,user_id               | PRIMARY       | 16      | const, messages.entity_id              |        1 | Using index   |

テーブルステートメントの作成:

〜5000行

subscription | CREATE TABLE `subscription` (
  `user_id`   bigint(20) unsigned NOT NULL,
  `entity_id` bigint(20) unsigned NOT NULL,
  PRIMARY KEY (`user_id`,`entity_id`),
  KEY `entity_id` (`entity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

~255,000 行

messages | CREATE TABLE `messages` (
  `message_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `entity_id` bigint(20) unsigned NOT NULL,
  `message` varchar(255) NOT NULL DEFAULT '',
  `timestamp` int(10) unsigned NOT NULL,
  PRIMARY KEY (`message_id`),
  KEY `entity_id` (`entity_id`,`timestamp`),
  KEY `idx_timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 
4

1 に答える 1

3

インデックス messages.entity_id を削除します。これは冗長です。straight_join を試してください。mysql オプティマイザーが間違った順序でテーブルにアクセスしていると思います。MySQL は最初にテーブル メッセージにアクセスする必要があるため、メッセージ (entity_id、タイムスタンプ) のインデックスを使用し、「一時的なものを使用する; ファイルソートを使用する」必要がなくなります (MySQL が MyISAM ディスク ベースのテーブルを作成する必要があり、ソート (クイックソート アルゴリズム) をディスク I/O 読み取りと I/O 書き込みで)。

 SELECT STRAIGHT_JOIN messages.*
   FROM messages
   INNER JOIN subscription ON subscription.entity_id = messages.entity_id
   WHERE subscription.user_id = 1
   ORDER BY messages.timestamp DESC 
   LIMIT 50

また

 SELECT messages.*
   FROM messages
   STRAIGHT_JOIN subscription ON subscription.entity_id = messages.entity_id
   WHERE subscription.user_id = 1
   ORDER BY messages.timestamp DESC 
   LIMIT 50

私もこの問題を抱えていて、このhttp://sqlfiddle.com/#!2/b34870/1のように修正しましたが、Country / Cityテーブルで修正しました

STRAIGHT_JOIN で Jason M の反応がオフになったため編集

次の 2 つの場合、直接結合は非効率的です。

サブスクリプション テーブルに user_id エントリがありません

実際、INNER JOIN を使用した MySQL オプティマイザは、「const テーブルを読み取った後に認識されない不可能な WHERE」をトリガーし、クエリを実行しません。ただし、STRAIGHT_JOIN は「const テーブルの読み取り後に認識できない WHERE に気付く」をトリガーしないため、(おそらく完全な) インデックス スキャンを実行して、クエリの実行を遅くする可能性のある user_id 値を見つける必要があります。 簡単な修正: STRAIGHT_JOIN で既存の user_id を使用する

メッセージ テーブルには関連するエントリがほとんどありません

ここで同じ問題が発生する可能性があります。しかし、確実に知るにはEXPLAINステートメントを見る必要があります

最初にこのクエリを試すこともできます

SELECT 
 *
FROM (

 SELECT
  entity_id

 FROM
  subscriptions

 WHERE
  subscription.user_id = 1 
)
 subscriptions

INNER JOIN 
 messages

ON
 subscriptions.entity_id = messages.entity_id

ORDER BY
 messages.timestamp DESC

LIMIT 50  
于 2013-10-05T17:42:55.260 に答える