【问题标题】:MySql - Self Join - Full Table Scan (Cannot Scan Index)MySql - 自连接 - 全表扫描(无法扫描索引)
【发布时间】:2016-11-24 12:00:52
【问题描述】:

我有以下自联接查询:

SELECT A.id
FROM mytbl      AS A
LEFT JOIN mytbl AS B 
ON (A.lft BETWEEN B.lft AND B.rgt)

查询很慢,查看执行计划后,原因似乎是 JOIN 中的全表扫描。该表只有 500 行,并且怀疑这是问题,我将其增加到 100,000 行,以查看它是否对优化器的选择产生了影响。 它没有,有 100k 行它仍在进行全表扫描。

我的下一步是尝试使用以下查询强制索引,但出现了同样的情况,全表扫描:

SELECT A.id
FROM categories_nested_set      AS A
LEFT JOIN categories_nested_set AS B 
FORCE INDEX (idx_lft, idx_rgt)
ON (A.lft BETWEEN B.lft AND B.rgt)

所有列(id、lft、rgt)都是整数,都被索引了。

为什么MySql在这里做全表扫描?

如何更改我的查询以使用索引而不是全表扫描?

CREATE TABLE mytbl ( lft int(11) NOT NULL DEFAULT '0', 
 rgt int(11) DEFAULT NULL, 
 id int(11) DEFAULT NULL,
 category varchar(128) DEFAULT NULL,
  PRIMARY KEY (lft), 
  UNIQUE KEY id (id), 
  UNIQUE KEY rgt (rgt), 
  KEY idx_lft (lft), 
  KEY idx_rgt (rgt) ) ENGINE=InnoDB DEFAULT CHARSET=utf8

谢谢

【问题讨论】:

  • 分享show create table xyz对每个相关xyz的结果
  • 结果如下:CREATE TABLE mytbl ( lft int(11) NOT NULL DEFAULT '0', rgt int(11) DEFAULT NULL, id int(11) DEFAULT NULL, category varchar(128) DEFAULT NULL, PRIMARY KEY (lft), UNIQUE KEY id (id), UNIQUE KEY rgt (rgt), KEY idx_lft (lft), KEY idx_rgt (rgt) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  • A PRIMARY KEYUNIQUE 键是 KEY。所以这两个KEYs是多余的,应该去掉。

标签: mysql indexing self-join mptt full-table-scan


【解决方案1】:

您有很多索引,其中一些是多余的。让我们从清理其中一些开始。过多的索引会减慢插入和更新速度。

PRIMARY KEY (lft),
KEY idx_lft (lft), 

既然你已经在 lft 上定义了一个主键,那么就没有必要再为 lft 上的另一个索引做任何事情了。同样,对于 rgt 上的唯一索引,不需要下面列出的第二个索引。

UNIQUE KEY rgt (rgt), 
KEY idx_rgt (rgt)

现在让我们看看您的查询。

SELECT A.id
FROM mytbl      AS A
LEFT JOIN mytbl AS B 
ON (A.lft BETWEEN B.lft AND B.rgt)

这不太可能是在野外遇到的查询。如果有 500 行,这个查询甚至可能产生 5000 行?您真的需要一次性创建完整的密钥吗?这个查询慢的原因是mysql对于常量只能optimize range comparisions。您的实际查询更有可能看起来像这样:

SELECT B.*
FROM mytbl      AS A
LEFT JOIN mytbl AS B 
ON (A.lft BETWEEN B.lft AND B.rgt) 
WHERE a.id = N;

为特定 id 创建节点的位置。这将使用索引并且会非常快。优化一个你不会经常使用的查询有什么意义?

【讨论】:

  • 感谢您的回复,我已经用一些额外的信息更新了我的问题。基本上我不能用 WHERE 子句来做,因为它是更大的 JOIN 的一部分。为了简单起见,我为这个问题去掉了它。在真正的 JOIN 用例中,它不使用索引。在更大的 JOIN 场景中,这是为了范围比较而考虑的常量还是必须是用户定义的常量?在这种情况下如何避免表扫描?谢谢
  • 那是移动球门柱并移动很远的距离
  • 我做了一些性能测试,这个 mytbl 的大小对将我的数据加载到另一个系统产生了最大的影响。如果我有 10k 行而不是 500 行,性能会降低 6000%。现在,这需要4个小时,而且只会变得更糟。所以我真的很感激知道如何让 MySql 使用索引进行范围查询
  • 你看,这一切都与你最初提出的问题完全不同。请接受这个答案,让我们继续提出一个新问题。正如我在回答中提到的,范围优化只发生在常量上。您发布完整的查询(请提供确切的查询)及其解释输出。同时发布其他涉及的表格。
  • 谢谢@AndrasDeak 我被变色龙问题烧了很多次。这是我第一次因为这个原因而真正回滚编辑,但不会是最后一次。
【解决方案2】:

以下 SO 问题对于解决方案至关重要,因为关于邻接列表和索引组合的信息非常少:

MySQL & nested set: slow JOIN (not using index)

似乎添加基本比较条件会触发索引的使用,如下所示:

SELECT A.id
FROM mytbl      AS A
LEFT JOIN mytbl AS B ON (A.lft BETWEEN B.lft AND B.rgt)
-- THE FOLLOWING DUMMY CONDITIONS TRIGGER INDEX
WHERE A.lft > 0
AND B.lft > 0
AND B.rgt > 0

不再进行表扫描。

编辑:查询的固定版本和非固定版本之间的 EXPLAIN 函数比较:

【讨论】:

  • 请在有和没有“修复”的情况下测试以下内容:FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%'; 如果它们之间的数字相同,那么仍然存在“全面扫描”,但可能在索引中而不是在桌子。
  • 谢谢 Rick,下面的数字(不包括零数字): WITH FIX 'Handler_commit','1' 'Handler_external_lock','4' 'Handler_read_first','2' 'Handler_read_key','2' ' Handler_read_next','646' WITHOUT FIX 'Handler_commit','1' 'Handler_external_lock','4' 'Handler_read_first','72' 'Handler_read_key','72' 'Handler_read_rnd_next','37941'
  • 这让我相信修复有所帮助。
  • 也许EXPLAIN SELECT ... 会指出它决定做不同的事情。
  • 我已经添加了上面结果的截图
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-31
  • 2011-06-28
  • 2011-03-12
  • 2021-09-04
相关资源
最近更新 更多