带有子查询的 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(*) 查询在某些表上很慢,而在其他表上却没有?