【问题标题】:Optimizing MySQL query: long list of IN vs narrowing down search first优化 MySQL 查询:一长串 IN vs 先缩小搜索范围
【发布时间】:2017-08-27 00:56:29
【问题描述】:

当使用大的 IN 列表进行 SELECT 时,首先缩小搜索范围会更快吗?

假设:

  • id 是主键
  • 表格有 2M 行
  • @list_of_id 中的 ID 总是靠近在一起,但不是连续的(选择在同一个月左右创建的一组人的子集)

当@list_of_id 长度为 1K、10K 或 100K 时,以下查询应如何执行?

查询 1:

SELECT * FROM people
WHERE id IN (@list_of_id);

查询 2a(先缩小搜索范围):

SELECT * FROM people
WHERE id <= @max_id
    AND id >= @min_id
    AND id IN (@list_of_id);

查询 2b(可能更慢):

SELECT * FROM people
WHERE MONTH(created) = @month
    AND id IN (@list_of_id);

【问题讨论】:

  • 您能以JOIN 的身份执行此操作吗?
  • 很抱歉,您试过了吗?你观察到了什么?如果没有进一步的细节,我们将无法确定性能:是否有指数?还有其他键吗? id 的数据类型是什么?
  • 如果您想知道哪个更快,全部尝试。无论如何,您都需要 id 上的索引,因此请确保先完成。
  • 除非您更改某些默认数据包大小设置,否则查询的大小将受到限制(即,您可能无法在不摆弄数据库/服务器设置的情况下对 100k 个项目执行IN) .对于所有这些,我会将您的查找 ID 列表放在一个临时表中并执行JOINcreated 日期上的索引也可能不是一个坏主意,然后将临时表方法与 2b 结合起来。
  • 如何开始生成 id 值列表?

标签: mysql sql select optimization


【解决方案1】:

MySQL 5.6 引入了一些优化器改进,专门针对 IN() 谓词中的长 id 列表。阅读https://dev.mysql.com/doc/refman/5.6/en/range-optimization.html#equality-range-optimization

如果您依赖IN() 搜索的索引,则该优化是相关的。如果您在查询 2a 中首先缩小搜索范围,则索引将用于不等式表达式,但不用于 IN() 谓词。

一般来说,当您有多个搜索词时,只有一个范围谓词会使用索引进行优化。范围谓词不是=

所以在您的 2a 示例中,BETWEEN 谓词将使用索引(我相信您使用的这对不等式表达式将被优化为好像它是一个 BETWEEN 谓词),然后 IN() 谓词将只需线性搜索第一次索引扫描的结果。

在您的 2b 示例中,尝试使用 MONTH(created) 缩小搜索范围根本无法使用索引。想想在电话簿中查找人——如果我要求你找到每个中间首字母为“J”的人。那么电话簿按姓氏排序这一事实对您没有帮助。

您可能想观看我的演示文稿How to Design Indexes, Really,或视频:https://www.youtube.com/watch?v=ELR7-RdU9XU

【讨论】:

  • 谢谢@bill-karwin!这是一个非常好的洞察力。立即观看视频。
【解决方案2】:

使用查询 1。

一个简单的IN(long-list) 将跳过索引(在您的情况下为PRIMARY KEY)。这将比您的任何替代方案更有效。

你可以通过这样做来“证明”这一点

FLUSH STATUS;
SELECT ...
SHOW SESSION STATUS LIKE 'Handler%';

当我在IN 中尝试了 146 个项目时,我得到了

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_external_lock      | 2     |
| Handler_mrr_init           | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 146   |  <-- note
... (other things with "0")

这表示它实际上只探测了索引 146 次。

我在一个 IN 列表中看到了 70K 项。查询花费了一些时间,但它并没有终止,甚至相当快,考虑到结果集有 70K 行。

(警告:我测试了 5.6.22;一些旧版本(5.1?)可能运行效率较低。)

如果您的 id 列表已经在另一个表中,则 JOIN 到该表。这比从服务器中取出 id 并将它们发送回服务器要快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-16
    • 1970-01-01
    • 1970-01-01
    • 2017-02-24
    • 2023-01-28
    • 2014-08-15
    相关资源
    最近更新 更多