通过 LEFT JOIN 优化 SQL 子查询

Posted

技术标签:

【中文标题】通过 LEFT JOIN 优化 SQL 子查询【英文标题】:Optimizing SQL subquery through a LEFT JOIN 【发布时间】:2015-09-16 18:32:17 【问题描述】:

我想根据 uniqueEntries 中不存在的实际条目 User_ID 将实际条目表中的所有记录插入到唯一条目表中。

我从一个包含NOT IN子查询的sql子句开始,它非常慢(在操作400K记录时),然后将其变成了LEFT JOIN子句,但速度并没有提高。

以下是我原来的包含NOT IN子查询的sql子句:

INSERT INTO uniqueEntries 
  SELECT * 
  FROM actualEntries 
  WHERE actualEntries.User_ID NOT IN (
    SELECT uniqueEntries.User_ID 
    FROM uniqueEntries
  )
  GROUP BY User_ID"

以下是转换成LEFT JOIN后的sql子句:

INSERT INTO uniqueEntries 
  SELECT actualEntries.* 
  FROM actualEntries 
  LEFT JOIN uniqueEntries 
  ON uniqueEntries.User_ID = actualEntries.User_ID 
  WHERE uniqueEntries.User_ID IS NULL 
  GROUP BY User_ID

当我对 50 条记录运行这两个查询时,它们会立即完成,但是当我对 400K 记录运行它们时,它们不会完成。

完成此操作的最快方法是什么?

更新/解决方案: 根据@Rahul、@Steve E 和@fhthiella,我将 LEFT JOIN 更新如下,并将 470K 记录的处理时间减少到 2 分钟。

INSERT INTO uniqueEntries 
  SELECT actualEntries.* 
  FROM actualEntries 
  LEFT JOIN uniqueEntries 
  ON uniqueEntries.id = actualEntries.id 
  WHERE uniqueEntries.User_ID IS NULL GROUP BY User_ID

【问题讨论】:

【参考方案1】:

在 uniqueEntries.User_ID 上放置唯一键或主键。那么

INSERT IGNORE INTO uniqueEntries 
  SELECT actualEntries.* 
  FROM actualEntries

IGNORE 子句将使 mysql 在插入过程中跳过错误。 这就是the manual 所说的:

如果您使用 IGNORE 关键字,则会出现错误 执行 INSERT 语句时发生的事件将被忽略。为了 例如,没有 IGNORE,复制现有 UNIQUE 的行 表中的索引或 PRIMARY KEY 值导致重复键错误 并且该语句被中止。使用 IGNORE,该行被丢弃并且没有 发生错误。忽略的错误可能会生成警告,尽管 重复键错误不会。

【讨论】:

Insert Ignore 部分帮助了我的请求。【参考方案2】:

首先删除GROUP BY 子句GROUP BY User_ID,因为它根本不需要。此外,您应该在 User_ID 列上为表 uniqueEntriesactualEntries 建立索引,因为您将其用作连接列。这样,您的查询应该看起来像

INSERT INTO uniqueEntries 
  SELECT actualEntries.* 
  FROM actualEntries 
  LEFT JOIN uniqueEntries 
  ON uniqueEntries.User_ID = actualEntries.User_ID 
  WHERE uniqueEntries.User_ID IS NULL 

【讨论】:

我将 uniqueEntries 和 actualEntries 上的 .User_ID 更改为 .id(有效)。我确实需要 GROUP BY,因为有些条目是重复的,我只想要唯一的条目 @xited,我仍然相信你不需要 GROUP BY 因为 where 语句 WHERE uniqueEntries.User_ID IS NULL 会按照你说的做。 @Rahul 不完全是 sqlfiddle.com/#!9/096ed8/1 在这种情况下仍然需要 group by(或者最好使用主键,然后使用 INSERT IGNORE sqlfiddle.com/#!9/e3131/1 @Rahul:uniqueEntries 不包含具有相同 User_ID 的多条记录,但 actualEntries 包含。 GROUP BY 应该确保我只在从输出中删除 uniqueEntries 后才从 actualEntries 中提取唯一记录,对吧? @xited,那么是的,您可能需要一个分组;但我会首先使用 group by 获得分组结果,然后使用 unique_entries 执行连接。否则,使用insert ignore ... 构造。【参考方案3】:

您应该在 uniqueEntries.User_ID 和 actualEntries.User_ID 字段上添加索引:

ALTER TABLE uniqueEntries ADD INDEX idx_ue_id (User_ID);
ALTER TABLE actualEntries ADD INDEX idx_ae_id (User_ID);

这应该使连接更快。我还看到您正在选择所有表格字段:

SELECT actualEntries.*

但是你是按 User_id 分组的

GROUP BY User_ID

我认为您这样做是因为每个 User_ID 可能有多行。 MySQL 允许您这样做,但请注意,如果有多行,您的查询将只保留一个,但未分组的值将是不确定的(它们可以属于任何分组的行)。

【讨论】:

@fhthiella 是多行。没错,我不在乎选择哪一个,只要我检索到唯一的行即可。 @xited 好的,所以您可以使用这样的 GROUP BY,只要您知道自己在做什么;)但是如果您希望 uniqueEntries 的 User_ID 是唯一的,我认为您应该接受 steve e 回答,因为它更优雅!

以上是关于通过 LEFT JOIN 优化 SQL 子查询的主要内容,如果未能解决你的问题,请参考以下文章

Sql查询left join

SQL LEFT JOIN 子查询别名

HiveSql&SparkSql —— 使用left semi join做inexists类型子查询优化

mysql 优化慢复杂sql (多个left join 数量过大 order by 巨慢)

ORACLE LEFT JOIN 子查询 在SQL SERVER中可以使用如图中的子查询,ORACLE中怎么实现

带有子查询语法的 LEFT OUTER JOIN