带有子查询的 CTE 查询在小型索引表上很慢;如何在 MySQL 上进行优化?

Posted

技术标签:

【中文标题】带有子查询的 CTE 查询在小型索引表上很慢;如何在 MySQL 上进行优化?【英文标题】:CTE query with subquery is v. slow on small indexed table; how to optimize on MySQL? 【发布时间】:2020-11-13 16:05:56 【问题描述】:

我最近将一个复杂的查询重写为带有子查询的 CTE 查询,部分原因是为了了解有关窗口函数的更多信息。新的很慢;对于只有几千行的索引表,在快速硬件上大约需要 0.5 秒。我不确定如何解释EXPLAIN;它似乎表明,正如我所料,没有多少行并且有索引。我需要在这里做什么?

这是在 mysql 上运行的;这个查询是由 SQLAlchemy 生成的,但我最初是用 SQL 手工编写了它的核心,然后才将它移植过来。这样做的目的是在单词列表中查找附近的单词,并排除相关主题。

WITH rowlist AS (
  SELECT
    word.id AS id,
    word.word AS word,
    word.part_of_speech AS part_of_speech,
    row_number() OVER (
      ORDER BY
        word.stripped_word,
        (
          SELECT
            min(quotations.date) AS min_1
          FROM
            quotations
          WHERE
            quotations.word_id = word.id
        )
    ) AS rownumber
  FROM
    word
  WHERE
    word.deleted IS NULL
    AND NOT (
      EXISTS (
        SELECT
          1
        FROM
          word_subject AS word_subject_1,
          word_subject
        WHERE
          word_subject_1.word_id = word.id
          AND word_subject_1.subject_id = 12
      )
    )
)
SELECT
  rowlist.rownumber AS rowlist_rownumber
FROM
  rowlist
WHERE
  rowlist.id = 392

EXPLAIN 是:

*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: <derived2>
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1543
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: DERIVED
        table: word
         type: ref
possible_keys: deleted
          key: deleted
      key_len: 6
          ref: const
         rows: 1543
        Extra: Using index condition; Using where; Using temporary
*************************** 3. row ***************************
           id: 4
  select_type: MATERIALIZED
        table: word_subject_1
         type: ref
possible_keys: word_id,subject_id
          key: subject_id
      key_len: 4
          ref: const
         rows: 503
        Extra: Using index
*************************** 4. row ***************************
           id: 4
  select_type: MATERIALIZED
        table: word_subject
         type: index
possible_keys: NULL
          key: word_id
      key_len: 8
          ref: NULL
         rows: 2415
        Extra: Using index; Using join buffer (flat, BNL join)
*************************** 5. row ***************************
           id: 3
  select_type: DEPENDENT SUBQUERY
        table: quotations
         type: ref
possible_keys: word_id
          key: word_id
      key_len: 5
          ref: db.word.id
         rows: 4
        Extra: 
5 rows in set (0.001 sec)

【问题讨论】:

样本数据、期望的结果和对逻辑的解释会有所帮助。 order by 中窗口函数的子查询是非常不正统的。 此数据库保存 OED 风格词典的记录,其中每个单词都有_many 引用。此查询的目的是生成围绕特定单词的单词列表。单词的顺序是按字母顺序排列的(“stripped_word”是标准化为小写且没有标点符号/空格的单词),如果单词相同,则按报价表中最早的报价,因此是原始 order_by 中的子查询。 【参考方案1】:

你的子查询有这个:

    FROM
      word_subject AS word_subject_1,
      word_subject

但是 word_subject_1 和 word_subject 之间没有连接条件。因此它是笛卡尔积。您可以看到在 EXPLAIN 中报告为索引扫描(实际上与表扫描相同)和未索引连接“使用连接缓冲区”。

我认为您根本不需要在子查询中加入。事实上,您也不需要相关子查询。您可以在 CTE 中将其作为排除连接来执行,而无需使用子查询:

  FROM
    word
  LEFT OUTER JOIN word_subject
    ON word.id = word_subject.word_id AND word_subject.subject_id = 12
  WHERE
    word.deleted IS NULL
    and word_subject.word_id IS NULL

确保您在 word_subject 中的这对列上有一个复合索引:(word_id, subject_id)

【讨论】:

嗯。我在这些列上有一个复合索引。该子查询是由 SQLAlchemy 自动生成的,但我在许多其他地方使用它,而且它在其他任何地方都非常快。让我进一步探讨...... 索引是必需的,但真正的重点是您需要摆脱笛卡尔积和相关子查询。无论是由 SQLAlchemy 生成还是手工编码,都会导致问题。 是的,我正在努力。尝试了一堆没有成功的事情,最后在 SQLAlchemy 部分发布了寻求帮助。手指交叉!

以上是关于带有子查询的 CTE 查询在小型索引表上很慢;如何在 MySQL 上进行优化?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 count(*) 查询在某些表上很慢,而在其他表上却没有?

MySQL简单插入查询在“查询结束”步骤上很慢

临时表索引/性能帮助请求

我们如何在 sql server 的子查询中使用 CTE?

MySQL INSERT 在同一张表上使用带有 COUNT() 的子查询

中小型表上的左连接非常慢