【问题标题】:Any possibility to speed up WHERE IN or replace it with faster alternative?是否有可能加快 WHERE IN 或用更快的替代方案替换它?
【发布时间】:2020-10-04 04:01:54
【问题描述】:

我正在尝试在下面的查询中加快选择速度,WHERE IN 中有超过 1000 个项目

表:

CREATE TABLE `user_item` (
  `user_id` int(11) unsigned NOT NULL,
  `item_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`user_id`,`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

查询:

SELECT
    item_id
FROM
    user_item
WHERE
    user_id = 2
    AND item_id IN(3433456,67584634,587345,...)

IN 列表中有 1000 个项目,执行查询大约需要 3 秒。在这种情况下可以进行任何优化吗?此表中可能有数十亿行。是否可以通过其他数据库或编程方法更快地执行此操作?

更新:

解释的结果如下:

如果我在 IN(...) 语句中有 999 个项目:

+------+-------------+----------+-------+---------------+---------+---------+------+------+--------------------------+
| id   | select_type | table    | type  | possible_keys | key     | key_len | ref  | rows | Extra                    |
+------+-------------+----------+-------+---------------+---------+---------+------+------+--------------------------+
|    1 | SIMPLE      | user_item | range | PRIMARY       | PRIMARY | 8       | NULL |  999 | Using where; Using index |
+------+-------------+----------+-------+---------------+---------+---------+------+------+--------------------------+

如果我在 IN(...) 语句中有 1000 个项目:

+------+--------------+-------------+--------+---------------+---------+---------+--------------------+------+--------------------------+
| id   | select_type  | table       | type   | possible_keys | key     | key_len | ref                | rows | Extra                    |
+------+--------------+-------------+--------+---------------+---------+---------+--------------------+------+--------------------------+
|    1 | PRIMARY      | <subquery2> | ALL    | distinct_key  | NULL    | NULL    | NULL               | 1000 |                          |
|    1 | PRIMARY      | user_item    | eq_ref | PRIMARY       | PRIMARY | 8       | const,tvc_0._col_1 |    1 | Using where; Using index |
|    2 | MATERIALIZED | <derived3>  | ALL    | NULL          | NULL    | NULL    | NULL               | 1000 |                          |
|    3 | DERIVED      | NULL        | NULL   | NULL          | NULL    | NULL    | NULL               | NULL | No tables used           |
+------+--------------+-------------+--------+---------------+---------+---------+--------------------+------+--------------------------+

更新 2

我想解释一下为什么我需要在上面做:

我想让用户能够列出按 sort_criteria_1、sort_criteria_2 或 sort_criteria_3 排序的项目,并从列表中排除那些在 user_item 表中由给定 (n) 个用户标记的项目。

这是示例架构:

CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `item` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `file` varchar(45) NOT NULL,
  `sort_criteria_1` int(11) DEFAULT NULL,
  `sort_criteria_2` int(11) DEFAULT NULL,
  `sort_criteria_3` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_sc1` (`sort_criteria_1`),
  KEY `idx_sc2` (`sort_criteria_2`),
  KEY `idx_sc3` (`sort_criteria_3`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `user_item` (
  `user_id` int(11) NOT NULL,
  `item_id` int(11) NOT NULL,
  PRIMARY KEY (`user_id`,`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

以下是我如何获取按 sort_criteria_2 排序的项目,不包括 user_item 表中用户(300、6、1344、24)记录的项目:

SELECT
    i.id,
FROM
    item i
    LEFT JOIN user_item ui1 ON (i.id = ui1.item_id AND ui1.user_id = 300)
    LEFT JOIN user_item ui2 ON (i.id = ui2.item_id AND ui2.user_id = 6)
    LEFT JOIN user_item ui3 ON (i.id = ui3.item_id AND ui3.user_id = 1344)
    LEFT JOIN user_item ui4 ON (i.id = ui4.item_id AND ui4.user_id = 24)
WHERE
    ui1.item_id IS NULL
    AND ui2.item_id IS NULL
    AND ui3.item_id IS NULL
    AND ui4.item_id IS NULL
ORDER BY
    v.sort_criteria_2
LIMIT
    800

上述方法的主要问题是我过滤的用户越多,查询的成本就越高。我希望客户端浏览器支付过滤费用。因此,我会将每个用户的项目列表和匹配的 user_item 记录列表发送到客户端进行过滤。这也有助于分片,因为我不必在同一台机器上拥有 user_item 表或记录集。

【问题讨论】:

  • 您的表没有parent_id 列,您是如何创建表的?另外,parent_id 有索引吗?
  • 另外的理论问题。 item_id 对您的查询很重要,它过滤掉的项目的百分比是多少?您是否尝试过通过user_id 获取所有项目并以编程方式将它们过滤掉?
  • @Progman 将 999 项添加的结果解释为 1000 项,因为它们不同。
  • @Jim 每当我听到“某某不是一种选择”时,除非有正当理由,否则我会忽略该声明。我通常会继续忽略。无论如何声明,因为在这个宇宙中,很少有事情是真正“不是一种选择”。那么,究竟为什么“加入不是一种选择”呢?您能否向我们展示一些项目 ID 列表存储位置的架构?
  • 如何回答我关于项目 ID 列表来自何处的问题,也显示架构。就上下文而言,我的预感是加入会非常有效;也许以毫秒为单位返回第一行,但我需要查看架构。您能否提供一个有效的连接查询,尽管速度很慢?

标签: mysql mariadb innodb rdbms


【解决方案1】:

很难准确判断,但由于有许多恒定的item_id 值,解析庞大的查询可能会有延迟。

  1. 您是否尝试过仅获取 user_id 的所有值?由于该字段是PRIMARY KEY 中的第一个(主)字段,因此仍将使用相关索引。

  2. 您是否尝试过用子查询替换常量列表?例如,您可能对特定类型的项目感兴趣。

  3. 确保您使用Prepared statement 概念 - 至少在您的数据库和语言支持的情况下。这将保护您的代码免受可能的 SQL 注入并启用数据库内置查询缓存(如果您的数据库支持)。

【讨论】:

  • 1.我不需要用户的所有值,我需要基于 item_id 的特定值。 2. 子查询不起作用,我提供了 ID,我需要进行查找。 3.这是直接在mariadb cli中运行的,现阶段只关心性能。
  • 那就没有线索了——这个任务太简单了,有很多方法可以使它复杂化,但不知道如何在当前限制下进一步优化它。
  • 我已经看到 70K 项需要相当长的时间 - 可能是 2 秒。仅仅 1K 的 3 秒就有点奇怪了。
【解决方案2】:

您可以将它们放入带有索引的临时表中,然后将其与user_item-table 连接,而不是将 1000 个 item_id 放入 IN-clause。

如果您还拥有一个同时包含user_iditem_id 的索引,这将使查询变得最快。其余的取决于数据分布。

【讨论】:

  • 不,这在我的用例中甚至都不可行。每秒可能有数千个查询,临时表和连接不会更快。
  • 看起来 MariaDB 正在按照您在 1000 案例中的建议进行操作。
猜你喜欢
  • 2018-12-11
  • 2012-07-05
  • 2012-01-23
  • 2013-07-13
  • 2011-02-27
  • 2010-09-22
  • 2017-05-15
  • 1970-01-01
相关资源
最近更新 更多