Mysql:2个ID的主键内部连接给出 "Range checked for each record"

标签 mysql sql indexing explain

在对具有 2 个值的 PRIMARY 键(使用 IN 或 OR 构造)执行 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)

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

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

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

它应该是 2 行,句点。

感谢您对此的任何投入(做了一些研究但没有发现任何有值(value)的东西,除了“请添加一个索引”,这显然不适用于此处)

编辑:是的,我尝试了 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;

最佳答案

一般来说,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。最终,这可能是对性能的洗礼。取决于需要检查多少行数据,以及产生多少行作为结果。

关于Mysql:2个ID的主键内部连接给出 "Range checked for each record",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26720108/

相关文章:

mysql - 如何更新连接值总和的字段?

mysql - 如何获取分组行的 ID?

mysql - 如何为 MySQL 表建立索引,99% 的时间我需要查询 1% 的数据

perl - 如何使用一列索引值在单独的数据集中创建新列

mysql - 创建临时表后表不存在

MySQL ORDER BY 和 JOIN

mysql - 我真的不能编辑 MySQL 触发器,我必须删除它并创建一个新触发器吗?

mysql - 分组并计数不返回我期望的

SQL - GroupByDay 从早上 6 点到早上 6 点的最佳方式

mongodb - Mongo拒绝使用复合索引中的所有字段