即使子查询非常快,ORDER BY主键也非常慢

时间:2020-05-11 18:49:43

标签: mysql sql database performance primary-key

create table account_match
(
    id                 int(11) unsigned auto_increment primary key,
    smaller_account_id int(11) unsigned                    not null,
    bigger_account_id  int(11) unsigned                    not null,
    match_origin       varchar(50)                         null,
    created            timestamp default CURRENT_TIMESTAMP not null,
    constraint account_match_account_id_fk
        foreign key (smaller_account_id) references account (id)
            on delete cascade,
    constraint account_match_account_id_fk_2
        foreign key (bigger_account_id) references account (id)
            on delete cascade

create index account_match_smaller_account_id_bigger_account_id_index
    on account_match (smaller_account_id, bigger_account_id);
);
create table private_message
(
    id                  int(11) unsigned auto_increment primary key,
    sender_account_id   int(11) unsigned                     not null,
    receiver_account_id int(11) unsigned                     null,
    message             text                                 null,
    viewed              tinyint(1) default 0                 not null,
    created             timestamp  default CURRENT_TIMESTAMP not null,
    constraint private_message_account_id_fk
        foreign key (sender_account_id) references account (id)
            on delete cascade,
    constraint private_message_account_id_fk_2
        foreign key (receiver_account_id) references account (id)
            on delete cascade
);

create index private_message_receiver_account_id_sender_account_id_index
    on private_message (receiver_account_id, sender_account_id);

create index private_message_sender_account_id_receiver_account_id_index
    on private_message (sender_account_id, receiver_account_id);
SELECT account_match.*
FROM (
         SELECT account_match.id
         FROM account_match
         WHERE id IN (SELECT id
                      FROM account_match
                      WHERE smaller_account_id = 1
                      UNION ALL
                      SELECT id
                      FROM account_match
                      WHERE bigger_account_id = 1)
           AND NOT EXISTS(SELECT TRUE
                          FROM private_message
                          WHERE sender_account_id = smaller_account_id
                            AND receiver_account_id = bigger_account_id)
           AND NOT EXISTS(SELECT TRUE
                          FROM private_message
                          WHERE sender_account_id = bigger_account_id
                            AND receiver_account_id = smaller_account_id)
     ) match_ids
         JOIN account_match using (id)
ORDER BY id DESC
LIMIT 20
1   PRIMARY account_match       index   PRIMARY PRIMARY 4       20  100 Backward index scan
1   PRIMARY account_match       eq_ref  PRIMARY PRIMARY 4   miliar.account_match.id 1   100 Using where
6   DEPENDENT SUBQUERY  private_message     ref private_message_receiver_account_id_sender_account_id_index,private_message_sender_account_id_receiver_account_id_index private_message_sender_account_id_receiver_account_id_index 9   miliar.account_match.bigger_account_id,miliar.account_match.smaller_account_id  1   100 Using index
5   DEPENDENT SUBQUERY  private_message     ref private_message_receiver_account_id_sender_account_id_index,private_message_sender_account_id_receiver_account_id_index private_message_sender_account_id_receiver_account_id_index 9   miliar.account_match.smaller_account_id,miliar.account_match.bigger_account_id  1   100 Using index
3   DEPENDENT SUBQUERY  account_match       eq_ref  PRIMARY,account_match_smaller_account_id_bigger_account_id_index    PRIMARY 4   func    1   26.57   Using where
4   DEPENDENT UNION account_match       eq_ref  PRIMARY,account_match_smaller_account_id_bigger_account_id_index,account_match_account_id_fk_2  PRIMARY 4   func    1   5   Using where

查询的重点是获取ID为1的帐户的匹配项(当然,其他时间也将是其他ID),两个用户都不会互相发消息。主查询(子查询)在不到20毫秒的时间内完成,但是一旦我添加了ORDER BY ID DESC,它的时间就会增加到20秒。子查询返回了大约710k行,但是大小无关紧要,因为我试图按主键排序吗?有什么办法可以解决这个速度?

2 个答案:

答案 0 :(得分:0)

只需尝试以下操作即可查看(因为我发现您的大多数代码似乎已过时)

以下内容无效

SELECT *
FROM account_match
WHERE (smaller_account_id = 1 or bigger_account_id = 1)
    AND EXISTS(SELECT TRUE
                   FROM private_message
                   WHERE (sender_account_id != bigger_account_id or receiver_account_id != smaller_account_id)
                    AND  (sender_account_id != smaller_account_id or receiver_account_id != bigger_account_id))
    ORDER BY id DESC
    LIMIT 20

尝试一下

-- Create a Temp table and get data from the faster query into that.
CREATE TEMPORARY TABLE a_m_temp
SELECT account_match.id
FROM account_match
WHERE id IN (SELECT id
        FROM account_match
        WHERE smaller_account_id = 1
        UNION ALL
        SELECT id
        FROM account_match
        WHERE bigger_account_id = 1)
    AND NOT EXISTS(SELECT TRUE
            FROM private_message
            WHERE sender_account_id = smaller_account_id
            AND receiver_account_id = bigger_account_id)
    AND NOT EXISTS(SELECT TRUE
            FROM private_message
            WHERE sender_account_id = bigger_account_id
            AND receiver_account_id = smaller_account_id)

-- Inner Join the temp. table to the main

Select a.* from
account_match a
INNER JOIN a_m_temp t
ON a.id = t.id
Order by a.id
Limit 20

答案 1 :(得分:0)

“没关系,因为我正在尝试按主键权限进行排序” –否。优化程序可能会在复杂的WHERE子句上工作,然后根据以下条件对结果进行排序ORDER BY。只有使用INDEX才能完全进行过滤,它才会考虑是否也可以进行排序和LIMIT

此外,子查询还隐藏了IN子句,该子句在逻辑上是无序的。那就是隐藏在“派生”表(FROM子句中的子查询)中。

在更高版本的MySQL中,优化器将考虑为派生表创建索引。我在您的EXPLAIN中看不到;将由<auto-key>表示。

与此同时,我不知道为什么您要有额外的桌子和较小/较大的桌子。

我认为优化将涉及摆脱IN ( SELECT ... ),也许是这样:

     FROM account_match
     WHERE id IN (SELECT id
                  FROM account_match
                  WHERE smaller_account_id = 1
                  UNION ALL
                  SELECT id
                  FROM account_match
                  WHERE bigger_account_id = 1)
     WHERE NOT EXISTS ( ... )
       AND NOT EXISTS ( ... )

->

     FROM ( SELECT  id
                  FROM account_match
                  WHERE smaller_account_id = 1
                  UNION ALL
                  SELECT id
                  FROM account_match
                  WHERE bigger_account_id = 1 ) AS x
     JOIN account_match USING(id)
     WHERE NOT EXISTS ( ... )
       AND NOT EXISTS ( ... )

此外,account_match需要一个索引 bigger_account_id开头,因为UNION的两半需要不同的索引(在这种情况下)。

在使用多个表时,请限定(最好使用短别名)所有列名。例如,我需要找出这些表在WHERE sender_account_id = smaller_account_id中。