15

次のクエリは、ユーザーによる未読メッセージのリストを受信することを目的としています。これには3つのテーブルrecipientsが含まれます。ユーザーとメッセージIDの関係messages、メッセージ自体、およびmessage_readersどのユーザーがどのメッセージを読んだかを示すリストが含まれます。

クエリには確実に4.9秒かかります。これはパフォーマンスに深刻な悪影響を及ぼします。データベースが最終的に数桁大きくなることを期待しているため、特に心配です。確かに、これは本質的に重いクエリですが、データセットは小さく、直感的にははるかに高速であるように見えます。サーバーには十分なメモリ(32GB)があるため、データベース全体を常にRAMにロードする必要があり、ボックス上で他に何も実行されていません。

テーブルはすべて小さいです:

recipients: 23581
messages: 9679
message_readers: 2685

クエリ自体:

SELECT 
    m.*
FROM 
    messages m
INNER JOIN recipients r ON r.message_id = m.id
LEFT JOIN message_readers mr ON mr.message_id = m.id
WHERE
    r.id = $user_id
    AND (mr.read_by_id IS NULL OR mr.read_by_id <> $user_id)

説明計画は非常に簡単です。

+----+-------------+-------+--------+-----------------------------------+-----------------------------------+---------+--------------------------------+-------+-------------+
| id | select_type | table | type   | possible_keys                     | key                               | key_len | ref                            | rows  | Extra       |
+----+-------------+-------+--------+-----------------------------------+-----------------------------------+---------+--------------------------------+-------+-------------+
|  1 | SIMPLE      | r     | ref    | index_recipients_on_id            | index_recipients_on_id            | 768     | const                          | 11908 | Using where |
|  1 | SIMPLE      | m     | eq_ref | PRIMARY                           | PRIMARY                           | 4       | db.r.message_id                |     1 | Using index |
|  1 | SIMPLE      | mr    | ALL    | NULL                              | NULL                              | NULL    | NULL                           |  2498 | Using where |
+----+-------------+-------+--------+-----------------------------------+-----------------------------------+---------+--------------------------------+-------+-------------+

にインデックスがありmessage_readers.read_by_idますが、IS NULL条件のため、実際には使用できないと思います。

以下を除くすべてのデフォルト設定を使用しています。

key_buffer=4G
query_cache_limit = 256M
query_cache_size = 1G
innodb_buffer_pool_size=12G

ありがとう!

4

6 に答える 6

4

message_readersそれがのサブセットであると仮定しrecipientsて、次の変更を行うことをお勧めします。

  1. テーブルを取り除き、message_readersテーブル上のフラグに置き換えrecipientsます。これにより、nullチェックが削除され、結合が削除されます。

  2. おそらくすでにそうですが、メッセージのほとんどすべての検索は受信者に基づいているため、のクラスター化インデックスがでrecipientsid, message_idなくであることを確認してください。message_id, id

結果のSELECTは次のとおりです。

SELECT
    r.whatever,
    m.whatever,
    -- ...
FROM
    recipients r
    INNER JOIN messages m ON m.id = r.message_id
WHERE
    r.id = $user_id
    AND r.read_flag = 'N'

アップデート

既存のスキームを使用したクエリの正しいバージョンは次のとおりです。

SELECT
    r.whatever,
    m.whatever,
    -- ...
FROM
    recipients r
    INNER JOIN messages m ON r.message_id = m.id
    LEFT JOIN message_readers mr ON mr.read_by_id = r.id 
                                 AND mr.message_id = m.id
WHERE
    r.id = $user_id
    AND mr.read_by_id IS NULL

これは、クラスター化インデックスが期待されるものであることを前提としています。

recipients: id, message_id
messages: id
message_readers: read_by_id, message_id
于 2011-06-27T19:45:31.770 に答える
1

私が何かを見逃していない限り、メッセージテーブルはまったく必要ないようです。本当に必要なのは、このユーザーの受信者に表示され、このユーザーのmessage_readersには表示されないメッセージIDの数です。

私が真上にいる場合は、マイナスでやりたいことを達成できます。

SELECT count(message_id)
  FROM (
        SELECT r.message_id  
          FROM recipients r 
         WHERE r.id = $user_id
        MINUS
        SELECT mr.message_id
          FROM message_readers mr
         WHERE mr.read_by_id = $user_id
       )

これにより、結合が完全に回避されます。これで、本番クエリのメッセージテーブルのデータが本当に必要な場合は、メッセージテーブルをこのサブクエリに結合できます(またはIN句に固定できます)。

私の経験はOracleランドであるため、ここで拠点を離れている可能性がありますが、MySQLはMINUSをサポートしているため、これはおそらく一見の価値があります。

于 2011-06-27T20:01:09.077 に答える
1

クエリに表示されているカウントだけが必要だとすると、そのように結合を変更するとどうなりますか?

私はMSSQLを使用していますが、これにより速度が向上する可能性があります。私はMySQLを使ったことがありませんが、うまくいくはずですよね?

SELECT     count(m.id)
FROM       messages m
INNER JOIN recipients r ON r.message_id = m.id AND r.id = $user_id
LEFT JOIN  message_readers mr ON mr.message_id = m.id AND (mr.read_by_id IS NULL OR mr.read_by_id <> $user_id)

編集:狂った考えのためにこれはどうですか?ORを2つの別々の左結合に分割し、それらのいずれかが何かを返した場合にレコードを取得できると思いました。

SELECT     count(m.id)
FROM       messages m
LEFT JOIN  recipients r ON r.message_id = m.id AND r.id = $user_id
LEFT JOIN  message_readers mr ON mr.message_id = m.id AND mr.read_by_id IS NULL
LEFT JOIN  message_readers mr2 ON mr2.message_id = m.id AND mr2.read_by_id <> $user_id
WHERE      COALESCE(mr.message_id, mr2.message_id) IS NOT NULL
于 2011-06-27T19:02:28.353 に答える
1

次のようにクエリを書き直すと、ISNULL条件を取り除くことができます。

SELECT 
    count(m.id)
FROM 
    messages m
INNER JOIN recipients r ON re.message_id = m.id
WHERE r.id = $user_id
  AND NOT EXISTS
         (SELECT mr.id 
            FROM message_readers mr 
           WHERE mr.message_id = m.id
             AND mr.read_by_id = $user_id)

基本的に、これは次のようになります。ない場所ですべてmessagesを取得し、問題のシンペラーについて説明します。recipientmessage_readers

于 2011-06-27T19:08:58.857 に答える
1

クエリ時間は何ですか

select distinct message_id
  from message_readers
 where read_by_id <> $user_id

注:nullは何にも等しくないため、「isnull」ロジックはこれによってキャッチされる必要があります

これが速い場合は、これを試してください。

SELECT count(m.id)
FROM messages m
INNER JOIN recipients r ON r.message_id = m.id
where r.id = $user_id
and m.id in (
    select distinct message_id
      from message_readers
     where read_by_id <> $user_id)

元の回答が機能しませんでした: 受信者のカバーインデックスにmessage_idとidを含めてみて、何が起こるかを確認してください。

于 2011-06-27T19:25:16.023 に答える
1

コメントcount(m.id)は、null値ではなく、m.idがnullになることはないため、余分な値をカウントすることを意味します。よくやってみてください

SELECT count(*)
FROM 
messages m
INNER JOIN recipients r ON r.message_id = m.id  
left join 
(
    select m.id
    messages m
    INNER JOIN message_readers mr 
    ON mr.message_id = m.id     
    and (mr.read_by_id <> $user_id or mr.read_by_id IS NULL)        
)as sub 
on sub.id = m.id        
WHERE r.id = $user_id

すべてのユーザーが着信メッセージを読み取ることができる理由(mr.read_by_is null)と、他のユーザーのためにメッセージを読み取ることができる理由、または特定の受信者ではない理由(mr.read_by_id <> $ user_id)

そのプール、私は推測します

より良いアプローチの1つは、サブクエリの内部を存在によって変更することです。「mr.read_by_idISNULL」は必須ではないことを確認してください。つまり、mr_read_by_idがnullの場合は、「mr.read_by_id =$user_id」がfalseであることを意味します。

SELECT count(*)
FROM 
messages m
INNER JOIN recipients r ON r.message_id = m.id  
left join 
(
    select m.id
    messages m
            where not exists(select * from message_readers mr 
    where mr.message_id = m.id      
    and mr.read_by_id = $user_id)
)as sub 
on sub.id = m.id        
WHERE r.id = $user_id
于 2011-06-27T20:05:31.467 に答える