mysql - MySQL JOIN 两个表和 ORDER BY 似乎没有使用正确的索引
问题描述
我在 MySQL 查询的性能方面遇到问题。总而言之,我想将表 A 连接到表 B,并根据表 A 中的表两列对结果进行排序。我的方法是在 (i) 连接表 A 和 B 的列上创建组合索引,并且 ( ii) 我想对结果排序的两列。但是,当我加入这两个表时,行为似乎出乎意料,并且该ORDER BY
子句似乎不再使用索引。
我使用以下查询:
SELECT SQL_NO_CACHE *
FROM
t_patent_documents A USE INDEX (idx_t_patent_documents_result_order) INNER JOIN
t_inv_title_int_content_combined B ON A.publication_id=B.publication_id
ORDER BY
A.language_id ASC,
A.result_order ASC
LIMIT 100
对于表t_patent_documents
,我将索引组合索引idx_t_patent_documents_result_order
定义为列上(publication_id, language_id, result_order)
。此外,publication_id
是 的主键t_patent_documents
。解释计划如下:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|----|-------------|-------|------------|------|-------------------------------------|-------------------------------------|---------|------------------|-----------|----------|-----------------------------------|
| 1 | SIMPLE | B | NULL | ALL | NULL | NULL | NULL | NULL | 132162247 | 100.00 | "Using temporary; Using filesort" |
| 1 | SIMPLE | A | NULL | ref | idx_t_patent_documents_result_order | idx_t_patent_documents_result_order | 4 | B.publication_id | 1 | 100.00 | NULL |
如果我执行以下操作(不强制索引):
SELECT SQL_NO_CACHE *
FROM
t_patent_documents A INNER JOIN
t_inv_title_int_content_combined B ON A.publication_id=B.publication_id
ORDER BY
A.language_id ASC,
A.result_order ASC
LIMIT 100
然后优化器选择只使用主键:
|----|-------------|-------|------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|---------|------------------|-----------|----------|-----------------------------------|
| 1 | SIMPLE | B | NULL | ALL | NULL | NULL | NULL | NULL | 132162247 | 100.00 | "Using temporary; Using filesort" |
| 1 | SIMPLE | A | NULL | eq_ref | "PRIMARY,idx_t_patent_documents_pubid_country_priority_dt,idx_t_patent_documents_pubid_priority_dt,idx_t_patent_documents_pubid_country_ucid,idx_t_patent_documents_pubid_ucid_priority_dt,idx_t_patent_documents_pubid_country_ucid_priority_dt,idx_t_patent_documents_result_order" | PRIMARY | 4 | B.publication_id | 1 | 100.00 | NULL |
现在,当我没有在表 A 上加入表 B 时,但ORDER BY
我定义了索引的三列,即(publication_id, language_id, result_order)
,它似乎正确地获取了索引。这里key_len
确实是 14:
SELECT SQL_NO_CACHE *
FROM
t_patent_documents A USE INDEX(idx_t_patent_documents_result_order)
ORDER BY
A.publication_id ASC,
A.language_id ASC,
A.result_order ASC
LIMIT 100
这导致以下解释计划:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|----|-------------|-------|------------|-------|---------------|-------------------------------------|---------|------|------|----------|-------|
| 1 | SIMPLE | A | NULL | index | NULL | idx_t_patent_documents_result_order | 14 | NULL | 100 | 100.00 | NULL |
有人理解这种行为吗?理想情况下,我将能够加入另一个表,并且仍然能够快速排序结果。
提前致谢!
更新 1:我也尝试publication_id
在ORDER BY
子句中添加。尽管从输出的角度来看,这根本没有意义,因为它publication_id
是独一无二的。查询如下所示:
SELECT SQL_NO_CACHE *
FROM
t_patent_documents A USE INDEX (idx_t_patent_documents_result_order) INNER JOIN
t_inv_title_int_content_combined B ON A.publication_id=B.publication_id
ORDER BY
A.publication_id ASC,
A.language_id ASC,
A.result_order ASC
LIMIT 100
由此产生的解释计划:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|----|-------------|-------|------------|------|-------------------------------------|-------------------------------------|---------|------------------|-----------|----------|-----------------------------------|
| 1 | SIMPLE | B | NULL | ALL | NULL | NULL | NULL | NULL | 132162247 | 100.00 | "Using temporary; Using filesort" |
| 1 | SIMPLE | A | NULL | ref | idx_t_patent_documents_result_order | idx_t_patent_documents_result_order | 4 | B.publication_id | 1 | 100.00 | NULL |
更新 2:
我还尝试在所有三列上运行没有多重索引的查询,但只在我要排序的两列上运行:(language_id, result_order)
称为x_temp_idx_1
. 这确实似乎在key_len
再次为 14 的意义上产生了效果,但是,现在连接部分需要永远。我运行的查询:
explain SELECT SQL_NO_CACHE A.language_id, A.result_order
FROM
t_patent_documents A USE INDEX(x_temp_idx_1) INNER JOIN
t_inv_title_int_content_combined B ON A.publication_id=B.publication_id
ORDER BY
A.language_id ASC,
A.result_order ASC
LIMIT 100
对应的解释方案:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|----|-------------|-------|------------|-------|---------------|--------------|---------|------|---------|----------|------------------------------------------------|
| 1 | SIMPLE | A | NULL | index | NULL | x_temp_idx_1 | 14 | NULL | 1795275 | 100.00 | "Using index; Using temporary; Using filesort" |
| 1 | SIMPLE | B | NULL | ALL | NULL | NULL | NULL | NULL | 2469412 | 10.00 | "Using where; Using join buffer (hash join)" |
请注意,此查询在信息较少的另一个数据库(开发)上运行。这就是为什么行数与之前的解释计划不对应的原因。
更新 3:
以上所有查询都是我想运行的实际查询的简化,以免问题过于复杂。在实际用例中,我需要过滤表 B 中的全文索引。该表在列上包含全文索引invention_title
:
explain SELECT SQL_NO_CACHE *
FROM
t_patent_documents A INNER JOIN
t_inv_title_int_content_combined B ON A.publication_id=B.publication_id
WHERE
MATCH(B.invention_title) AGAINST("+hydraulic" IN BOOLEAN MODE)
ORDER BY
A.language_id ASC,
A.result_order ASC
LIMIT 100
结果解释再次表明它仅使用主键进行连接,但未能使用多索引。如果我再次强制索引,key_len
则为 4,它实际上似乎并没有使用多索引:
| | | | | | | | | | | | |
|----|-------------|-------|------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|---------|------------------|------|----------|----------------------------------------------------------------------|
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
| 1 | SIMPLE | B | NULL | fulltext | idx_ft_inv_title_int_content_combined_inv_title | idx_ft_inv_title_int_content_combined_inv_title | 0 | const | 1 | 100.00 | "Using where; Ft_hints: no_ranking; Using temporary; Using filesort" |
| 1 | SIMPLE | A | NULL | eq_ref | "PRIMARY,idx_t_patent_documents_pubid_country_priority_dt,idx_t_patent_documents_pubid_priority_dt,idx_t_patent_documents_pubid_country_ucid,idx_t_patent_documents_pubid_ucid_priority_dt,idx_t_patent_documents_pubid_country_ucid_priority_dt,idx_t_patent_documents_result_order,x_temp_idx_2" | PRIMARY | 4 | B.publication_id | 1 | 100.00 | NULL |
解决方案
对于您的第一个查询,删除USE INDEX
并将索引更改为以下顺序:
(language_id, result_order, publication_id)
这个想法是有INDEX
匹配的ORDER BY
。优化器优先考虑WHERE
and GROUP BY
,但您也没有。因此,您的案例的目的是能够LIMIT
通过使索引与ORDER BY
. 这将很好地工作A
。
更改SELECT *
以仅指定必要的行可能会有所帮助。`
如果每个 A 行恰好有 1 个 B 行,那么还有另一个优化。由于情况并非如此,因此JOIN
必须执行,然后排序,最后才交付LIMIT
行。
回复:更新 3
这正如预期的那样。
- 使用 FT 索引来查找(希望)几行匹配。
- 以非常有效的方式进入另一张桌子,即通过 PK。
- 抓住需要的其他列。
- 排序实现
ORDER BY
。 - 剥离
LIMIT
行。
没有进一步的优化。一个可能的拖累是如果*
包含一些笨重的TEXT
或BLOB
列。
推荐阅读
- amazon-redshift - 为什么不使用带有交错排序键的时间戳?
- android - 用于检查 SPEN 是否附加在手机中的 API?
- gitlab - 使用共享运行器的 GitLab CI/CD 配置问题
- android - 转包在一个 Java 对象中出错警告(使用 safeUnbox 方法)
- javascript - jQuery Keypad / Primefaces 键盘重新映射空格键
- c# - 如何在 C# 中使用 Directory.GetFiles() 添加多个文件?
- php - 提交表单后,我在 POST 中获取“所有”“命名”对象时遇到问题
- javascript - 从另一个对象数组中过滤对象数组。两个数组都已从 mongodb 中提取
- sonata-admin - 在奏鸣曲中定义扩展实体
- python - 列表中的所有列表是否相等