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

Lid*_*oku 9 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
Run Code Online (Sandbox Code Playgroud)

在做解释时,它给了我:

+----+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------------------------+
| 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)|
+----+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------------------------+
Run Code Online (Sandbox Code Playgroud)

它不可能......

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

SELECT *
FROM message AS m
INNER JOIN user AS u
ON u.id IN(m.sender_id, m.receiver_id)
Run Code Online (Sandbox Code Playgroud)

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

SELECT *
FROM message AS m
INNER JOIN user AS u
ON u.id = m.sender_id
Run Code Online (Sandbox Code Playgroud)

这怎么可能?我正在加入具有相同类型值的主键.(实际的查询是"有点"更复杂,但没有什么花哨,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;
Run Code Online (Sandbox Code Playgroud)

Bil*_*win 6

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

您的连接条件具有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 |
+----+--------------+------------+--------+---------------+---------+---------+--------------------+------+-----------------+
Run Code Online (Sandbox Code Playgroud)

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