【问题标题】:MySQL optimizing join of 2 large tablesMySQL优化2个大表的连接
【发布时间】:2019-12-30 06:10:27
【问题描述】:

我知道这个问题之前已经被问过很多次了,但我已经尝试了那里提到的解决方案,但没有找到答案。 我不确定答案是否只是“不可能”,因此决定提出这个问题。

我有 2 张桌子,尺寸如下:

SELECT COUNT(*) from t1
24mill

SELECT COUNT(*) from t1 WHERE col1 = 1 and col2 = 'A';
395k

SELECT COUNT(*) from t2
6mill

SELECT COUNT(*) from t2 WHERE col3 != 'B';
5.9mill

注意:

  • col2col3 是只有 6 个唯一值的枚举。
  • col1 是具有约 9000 个唯一值的外键
  • t1 -> t2 是一对多 => t1 中的多个记录将链接到同一个 t2
  • 这些列都不能为空

我想要做的查询是:

SELECT COUNT(*) AS num
FROM t1 INNER JOIN t2 ON t1.t2_ID = t2.t2_ID
WHERE t1.col1 = 1 AND t1.col2 = 'A' AND t2.col3 != 'B';
395k

t2_ID 是 t2 的主键,是 t1 的外键。

我已经创建了索引:

ALTER TABLE t1 ADD KEY key1 (t2_id);
ALTER TABLE t1 ADD KEY key2 (col1, t2_id, col2);

解释:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: key1,key2
          key: key2
      key_len: 4
          ref: const
         rows: 660k
     filtered: 50.00
        Extra: Using where; Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: t2
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY,key3,key4
          key: PRIMARY
      key_len: 4
          ref: t1.t2_ID
         rows: 1
     filtered: 85.71
        Extra: Using where

当我运行这个查询时,它需要 3 秒。我正在尝试弄清楚如何才能使这更快。

在我看来,它已经在使用索引了。我正在尝试查看是否可以制作更好的索引或子查询或其他东西以使其更快。

【问题讨论】:

  • 面对现实,爬过 395K 行需要时间。 3 秒听起来很快。
  • 悲伤,但可能是真的。不过,我很想了解 3sec 的原因/原因。从某种意义上说——在这种情况下,我应该寻求改进哪些基础设施参数。我能理解 3 秒中有多少时间是因为数据加载(即来自 Amazon 的高 IOPS 磁盘会有所帮助吗?)还是仅仅是 RAM 周期(内存优化实例),或者可能只是 CPU - 或全部!如果没有软件解决方案有效,我可能不得不进行硬件优化
  • 什么版本的 MySQL?我认为较新的版本可以一些使用!=(但不是按照您用于索引的顺序)。
  • 5.7 - 我可以升级到最新的次要版本。由于很多原因,我必须使用 5.x。我目前在 Mac OSX 上使用 5.7.26
  • @TimBiegeleisen - MySQL 几乎总是使用“NLJ”(嵌套循环连接)。首先,它查看一个表(过滤掉不需要的行)。然后对于这些行中的每一行,它会到达下一个表。 (哪个表先出现是一种优化。)

标签: mysql sql join query-optimization


【解决方案1】:
select count(*) from t2 where
    t2.col3!='B'
    and not exists( select 1 from t1 where t1.t2_ID = t2.t2_ID and 
                    t1.col1 = 1 AND t1.col2 = 'A')

答案是一样的。

【讨论】:

  • 嗨 Yury,使用“不存在”给了我相反的意思 - 所以,我认为你的意思是“存在”。使用“存在”使其变为 20 秒
  • 是的,当然存在。请尝试: select count(*) from t2 where t2.col3!='B' and t2.t2_ID in (select t1.t2_ID from t1 where t1.col1 = 1 AND t1.col2 = 'A')
  • 您是否有理由认为这些更快?我的理解是 IN 不使用索引,因此会比连接慢。 IN 需要 9.5 秒。此外,刚刚意识到 - 这些都没有给出相同的结果,因为 t1 中可以有 2 条记录链接到 t1 中的同一记录 - 这是一对多
  • 看来你是对的。一个问题...您只尝试过一次吗?
  • 通常在测试时尝试查询 3 次以了解 MySQL 中缓冲区/缓存的影响
【解决方案2】:

WHERE 子句的条件对于行限制更为重要,因此查询将检查来自t1 的较少行。您应该首先在索引中索引col1col2,这样可以更精确地缩小搜索范围。然后将t2_id 添加为索引的第三列。

CREATE INDEX keyBK1 ON t1 (col1, col2, t2_id);

如果索引的第一列是t2_id,并且第二列有助于缩小搜索范围,您也可以避免连接到t2 的主键并直接连接到二级索引。

CREATE INDEX keyBK2 ON t2 (t2_id, col3);

我还没有测试它,但我希望 EXPLAIN 类似于以下内容(我已经模拟了,我实际上并没有运行 EXPLAIN):

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: key1,key2,keyBK1
          key: key3
      key_len: 8
          ref: const,const
         rows: ...about 300k... 
     filtered: 100.00
        Extra: Using where; Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: t2
   partitions: NULL
         type: ref
possible_keys: PRIMARY,key3,key4,keyBK2
          key: keyBK2
      key_len: 8
          ref: t1.t2_ID,const
         rows: 1
     filtered: 100.00
        Extra: Using where; Using index

如果您可以让两个表都报告“使用索引”,它应该会更快。

但是,如果在 t1 表上的搜索继续匹配 300k 行,则此查询总是会非常昂贵(即速度很慢)!

【讨论】:

  • 我创建了第一个索引,并尝试运行它 - 仍然需要 3 秒(说明显示它没有使用新索引)。但是当我执行USE INDEX(keyBK1) 时 - 它显示行数为 830k 而不是 300k。我没有尝试第二个索引 - 因为即使蒂姆建议它,索引也没有被使用
  • @AbdealiJK - 结果是 830K?还是EXPLAIN中的830K?
  • 在解释中 - 结果是一样的
  • @AbdealiJK - EXPLAIN 通常不精确,有时甚至不精确。对它的数字持保留态度。
  • 如果查询本身不使用新索引,您可能需要强制它这样做。要么删除混淆它的索引,要么使用索引提示。阅读dev.mysql.com/doc/refman/5.7/en/index-hints.html
【解决方案3】:

ALTER TABLE t1 ADD KEY key2 (col1, t2_id, col2) 应改为: ALTER TABLE t1 ADD KEY key2 (t2_id, col1, col2) 使列顺序从最有选择性到最无选择性。 t2_id 有 6m 个唯一值,col1 有 9000 个唯一值,col2 有 6 个唯一值。此更改将使索引更具选择性,查询更高效。

【讨论】:

  • 我认为 t2_id 作为第一个不是一个好主意,因为根据原始问题的 cmets 中的对话 - WHERE 将首先执行。所以,col1, col2, t2_id 是最优化的组合——我的理解是 col1、col2 的顺序并不重要——因为它们都是常量
  • @AbdealiJK 一个测试值得一千个意见。我支持我的答案,
  • Rick 有一个较早的答案,他提出了相同的建议,并且在不久前尝试过......花了 10 秒,并且对此进行了一些讨论。遗憾的是,他删除了该答案,因此该对话的唯一剩余部分是原始问题中的 cmets。
  • @AbdealiJK 不幸的是,由于数据的大小,除了您之外没有其他人可以测试这一点。
  • @AbdealiJK 我经常运行此查询,然后您可以将索引定义与您的定义保持一致。如果这是偶尔查询,那么您应该拥有我建议的索引。在数据空间、日志空间以及在插入、更新和删除时更新索引所需的时间方面,与表上的索引相关联的成本。您了解自己的系统,因此必须做出适当的决定。
猜你喜欢
  • 1970-01-01
  • 2012-08-01
  • 2012-12-17
  • 2017-04-22
  • 1970-01-01
  • 2021-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多