Mysql:2个ID的主键上的内连接给出"为每个记录检查范围"

时间:2014-11-03 17:59:10

标签: mysql sql indexing explain

在使用2个值(使用IN或OR结构)的PRIMARY键上进行INNER JOIN时,在EXPLAIN SELECT中得到“检查每个记录的范围(索引映射:0x1)”

以下是查询:

SELECT *
FROM message AS m
INNER JOIN user AS u
ON u.id = m.sender_id OR u.id = m.receiver_id

在做解释时,它给了我:

+----+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra                                         |
+----+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------------------------+
|  1 | SIMPLE      | u     | ALL  | PRIMARY       | null | null    | null | 75000 | Range checked for each record (index map: 0x1)|
+----+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------------------------+

不可能......

如果我尝试这个,我会得到相同的结果:

SELECT *
FROM message AS m
INNER JOIN user AS u
ON u.id IN(m.sender_id, m.receiver_id)

但是,如果我这样做,它工作正常,我只解析了一行:

SELECT *
FROM message AS m
INNER JOIN user AS u
ON u.id = m.sender_id

这怎么可能?我正在加入具有相同类型值的主键。 (实际的查询是“有点”更复杂,但没有什么花哨,2个内连接,最后一个左连接)

它应该是2行,句号。

感谢您对此提出任何意见(进行了一些研究,但除了“请添加索引”之外没有找到任何有价值的内容,这显然不适用于此处)

编辑:是的,我尝试了USE INDEX声明,但仍然没有运气

编辑:这是一个非常简单的模式来重现MySQL的这种奇怪的行为:

CREATE TABLE test_user (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(30),
    PRIMARY KEY (id)
);

CREATE TABLE test_message (
    id INT NOT NULL AUTO_INCREMENT, 
    sender_id INT NOT NULL,
    receiver_id INT NOT NULL,
    PRIMARY KEY (id),
    INDEX idx_sender (sender_id),
    INDEX idx_receiver (receiver_id)
);

EXPLAIN SELECT *
FROM test_message AS m
INNER JOIN test_user AS u
    ON u.id = m.sender_id OR u.id = m.receiver_id;

2 个答案:

答案 0 :(得分:5)

通常,MySQL在查询中每个表引用只能使用一个索引(有一个index-merge算法,但这并不像你想象的那样频繁。)

您的连接条件在两个与索引列的比较之间有OR,并且优化器无法选择在逐行检查表中的数据之前使用哪个更好。

常见的解决方法是在更简单的查询之间执行UNION,而不是OR条件。

mysql> EXPLAIN 
    SELECT * FROM test_message AS m 
    INNER JOIN test_user AS u ON u.id = m.sender_id 
  UNION
    SELECT * FROM test_message AS m 
    INNER JOIN test_user AS u ON u.id = m.receiver_id;

+----+--------------+------------+--------+---------------+---------+---------+--------------------+------+-----------------+
| id | select_type  | table      | type   | possible_keys | key     | key_len | ref                | rows | Extra           |
+----+--------------+------------+--------+---------------+---------+---------+--------------------+------+-----------------+
|  1 | PRIMARY      | m          | ALL    | idx_sender    | NULL    | NULL    | NULL               |    1 | NULL            |
|  1 | PRIMARY      | u          | eq_ref | PRIMARY       | PRIMARY | 4       | test.m.sender_id   |    1 | NULL            |
|  2 | UNION        | m          | ALL    | idx_receiver  | NULL    | NULL    | NULL               |    1 | NULL            |
|  2 | UNION        | u          | eq_ref | PRIMARY       | PRIMARY | 4       | test.m.receiver_id |    1 | NULL            |
| NULL | UNION RESULT | <union1,2> | ALL    | NULL          | NULL    | NULL    | NULL               | NULL | Using temporary |
+----+--------------+------------+--------+---------------+---------+---------+--------------------+------+-----------------+

这确实在两个子查询中都使用了正确的索引查找,但之后必须使用临时表来完成UNION。最终,它可能是性能的洗涤。取决于需要检查多少行数据,以及生成多少行。

答案 1 :(得分:1)

这个问题在其他(我认为全部)RDBMS中也是众所周知的,优化器每个连接只使用一个规则。

如果连接条件很复杂或者无法识别已知模式来解决它,则不会应用任何优化,它将用于全表扫描。

在你的情况下,主连接中的OR条件似乎很简单,但事实并非如此,因为你要求一次检查两个不同列(而不是常量值)的每个用户ID。

要解决此问题,您必须在更多子查询中拆分连接条件,以便优化器可以为每个子查询使用更好的规则。

@Bill Karwin提出了通用的解决方案,它有助于理解这个问题。

解决此问题的一种(稍微)更好的方法是将联合上移一级并加入派生表:

EXPLAIN 
SELECT *
FROM test_user AS u
INNER JOIN (
    select id, sender_id as msg_id
    from test_message 
    union all 
    select id, receiver_id 
    from test_message 
    ) AS m 
ON u.id = m.msg_id;

它不会使用TEMPORARY表,只会在test_users而不是两个

上进行一次全表扫描
id  select_type table           partitions  type    possible_keys   key             key_len ref         rows    filtered    Extra
1   PRIMARY     u               NULL        ALL     PRIMARY         NULL            NULL    NULL        1       100.00      NULL
1   PRIMARY     <derived2>      NULL        ref     <auto_key0>     <auto_key0>     4       test.u.id   2       100.00      NULL
2   DERIVED     test_message    NULL        index   NULL            idx_sender      4       NULL        1       100.00      "Using index"
3   UNION       test_message    NULL        index   NULL            idx_receiver    4       NULL        1       100.00      "Using index"