向索引列添加多个 OR 条件后,MySQL 查询速度变慢

时间:2021-02-03 16:32:27

标签: mysql

我有一个在 MySQL 上运行的查询(v5.5——我知道它很旧,但我现在必须使用它)。下面的表 A 有 ~1600 万行,B 有 ~700,000。查询如下所示:

SELECT A.id, A.x, A.y, A.z, B.foo FROM A STRAIGHT_JOIN B ON A.id = B.id
    where A.x = 53 ORDER BY A.y desc LIMIT 0, 30;  

A.idB.id 上都有索引设置。
(A.x, A.y) 上还有一个索引设置(此键/索引称为 DocsByType)。

到目前为止,这个查询效果很好,它的性能一直在亚秒级左右。不过最近,我需要偶尔检查 where 子句中 A.x 的其他可能值。以下查询现在执行得很差,平均需要大约 15 秒才能完成:

SELECT A.id, A.x, A.y, A.z, B.foo FROM A STRAIGHT_JOIN B ON A.id = B.id
    where (A.x = 18 or A.x = 53) ORDER BY A.y desc LIMIT 0, 30;  

只有一个比较的快速查询的 explain 如下所示:

+----+-------------+-------+------+-----------------------------------------------------+------------+---------+----------------+---------+-------------+
| id | select_type | table | type | possible_keys                                       | key        | key_len | ref            | rows    | Extra       |
+----+-------------+-------+------+-----------------------------------------------------+------------+---------+----------------+---------+-------------+
|  1 | SIMPLE      | A     | ref  | Documents1,Documents2,Documents3,DocsByType,KEY_AID | DocsByType | 4       | const          | 1870603 | Using where |
|  1 | SIMPLE      | B     | ref  | KEY_BID                                             | KEY_BID    | 4       | mydb.B.id      |       1 |             |
+----+-------------+-------+------+-----------------------------------------------------+------------+---------+----------------+---------+-------------+  

多重比较查询的 explain 如下所示:

+----+-------------+-------+-------+-----------------------------------------------------+------------+---------+----------------+---------+-----------------------------+
| id | select_type | table | type  | possible_keys                                       | key        | key_len | ref            | rows    | Extra                       |
+----+-------------+-------+-------+-----------------------------------------------------+------------+---------+----------------+---------+-----------------------------+
|  1 | SIMPLE      | A     | range | Documents1,Documents2,Documents3,DocsByType,KEY_AID | DocsByType | 4       | NULL           | 1878693 | Using where; Using filesort |
|  1 | SIMPLE      | B     | ref   | KEY_BID                                             | KEY_BID    | 4       | mydb.B.id      |       1 |                             |
+----+-------------+-------+-------+-----------------------------------------------------+------------+---------+----------------+---------+-----------------------------+ 

我可以看到有一个 filesort 操作不在第一个查询中。此外,type 是“range”而不是“ref”,ref 是“NULL”而不是“const”。删除 order by 子句可以完全修复它,使其在不到一秒的时间内完成,但对结果进行排序很重要。

查询优化不是我的强项。考虑到该列已经被编入索引,我会认为它的工作方式完全相同。任何人都可以解释为什么它的行为方式并建议优化查询的方法吗?另请注意,新查询可能需要为 where 子句使用 3、4 甚至 5 个可能的值(但始终针对同一列)。

我也尝试过使用 MySQL 5.8 运行查询,但结果是一样的。我的表使用的是 MyISAM 引擎。

1 个答案:

答案 0 :(得分:1)

假设您有一个很大的人名列表。目标是找到前 30 个 Smiths(按名字排序)。第一个查询很快,因为它本质上是同时执行 WHEREORDER BYLIMIT

第二个更麻烦,因为它有效地完成了:

  1. 找出所有“史密斯”的名字,
  2. 找出所有“琼斯”的名字
  3. 对名字进行排序并显示前 30 个

有两件事可以加快您的慢查询速度:

( SELECT A.id, A.x, A.y, A.z, B.foo FROM A JOIN B ON A.id = B.id
    where (A.x = 18)
    ORDER BY A.y desc LIMIT 30 )
UNION ALL  
( SELECT A.id, A.x, A.y, A.z, B.foo FROM A JOIN B ON A.id = B.id
    where (A.x = 53)    -- Note
    ORDER BY A.y desc LIMIT 30 )  
ORDER BY A.y desc LIMIT 0, 30    -- Yes, repeated

评论:

  • STRAIGHT_JOIN 是不必要的,JOIN 会做同样的事情
  • 每个子查询都将使用 INDEX(x,y) 并使用 LIMIT。
  • ALL 比默认值更快,适用于这种情况
  • 如果您需要“分页”,则需要按照此处所述处理限制:http://mysql.rjweb.org/doc.php/index_cookbook_mysql#or
  • 可以将任意数量的 UNIONs 拼接在一起。然而,在某个时候,所有工会的成本将超过收益。 (尝试预测截止点是不切实际的。)

LIMIT 30JOINing 之前执行 B 会更快。这样,您将只在 B 中执行 30 次查找;我的方式需要60;您的原始查询需要更多。