【问题标题】:Why is this update-with-join mysql query so slow?为什么这个 update-with-join mysql 查询这么慢?
【发布时间】:2013-09-18 11:37:21
【问题描述】:

我有一个应用程序需要更新分层结构中的节点,从已知 ID 的特定节点向上更新。我使用以下 MySQL 语句来执行此操作:

update node as A 
join node as B 
   on A.lft<=B.lft and A.rgt>=B.rgt 
set A.count=A.count+1 where B.id=?

该表在 id 上有一个主键,在 lft 和 rgt 上有一个索引。该语句有效,但我发现它存在性能问题。查看对应 select 语句的 EXPLAIN 结果,我看到为“B”表检查的行数非常大(可能是整个表)。

我可以轻松地将查询分成两个独立的:

select lft, rgt from node where id=?
LFT=result.lft
RGT=result.rgt
update node set count=count+1 where lft<=LFT and rgt>=RGT

但是为什么原始语句没有按预期执行,我需要如何重新制定它才能更好地工作?

根据要求,这里是创建表的缩写版本:

CREATE TABLE `node` ( 
`id` int(11) NOT NULL auto_increment, 
`name` varchar(255) NOT NULL, 
`lft` decimal(64,0) NOT NULL, 
`rgt` decimal(64,0) NOT NULL, 
`count` int(11) NOT NULL default '0', 
PRIMARY KEY (`id`), 
KEY `name` (`name`), 
KEY `location` (`location`(255)), 
KEY `lft` (`lft`), 
KEY `rgt` (`rgt`), 
) ENGINE=InnoDB

我没有尝试添加复合索引(实际上,我没有当场执行此操作所需的访问级别);但我看不出它有什么帮助,试图思考数据库引擎将如何尝试解决双重不等式。

【问题讨论】:

  • 如果您可以同时发布表定义和解释,这将有所帮助...
  • 你能发布CREATE TABLE 语句和`EXPLAIN 输出吗?
  • 顺便说一句:A.lft&lt;=B.lft and A.rgt&gt;=B.rgt 的条件对于 A==B 为真。这是预期的行为吗?
  • 是的,这是故意的 :-)
  • 表是 MyISAM 还是 InnoDB?

标签: mysql sql performance join


【解决方案1】:

您可以“强制”(至少到 5.5,版本 5.6 对优化器进行了多项改进,这可能会使这种重写变得多余)MySQL 通过将拆分的第一部分作为子查询,然后将其用作派生表并加入表 A:

UPDATE node AS a 
  JOIN 
    ( SELECT lft, rgt
      FROM node
      WHERE id = ? 
    ) AS b 
    ON  a.lft <= b.lft 
    AND a.rgt >= b.rgt
SET 
    a.count = a.count + 1 ; 

效率仍然取决于选择两个索引中的哪一个来限制要更新的行。仍然在使用这两个索引中的任何一个之后,还需要进行表查找来检查另一列。所以,我建议你在(lft, rgt) 上添加一个复合索引,在(rgt, lft) 上添加一个复合索引,这样只有一个索引用于查找应该更新的行。

我假设您使用的是嵌套集,并且此更新在大表上的效率不会很高,因为查询有 2 个范围条件,这限制了 B-tree 索引的效率。

【讨论】:

  • 感谢您的回复。我尝试通过解释运行它,它看起来并不乐观。对糟糕的格式表示歉意,解释输出是:1|PRIMARY||system|null|null|null|null|1|| 1|PRIMARY|a|ALL|lft,rgt|null|null|null|999331|使用 where| 2|DERIVED|节点|const|PRIMARY|PRIMARY|4||1|| -- 仍然显示大量要检查的行。
  • 另一方面,简单查询“select * from node where lft=?”产生以下解释: 1|SIMPLE|node|range|lft,rgt|lft|29|null|8|Using where|
  • 您可以编辑问题并使用询问的信息进行更新吗? (CREATE TABLE 语句、引擎等)?您是否添加了复合索引?
【解决方案2】:

我猜你最大的性能问题是你使用的不需要的JOIN。您只需执行两个小子查询即可完成此操作,而不是连接两个大表。

示例如下:

UPDATE node AS a
SET a.count = a.count+1 
WHERE a.lft <= (SELECT lft FROM node WHERE id = ?) 
AND  a.rgt >= (SELECT rgt FROM node WHERE id = ?)

【讨论】:

  • 这与@Guerra 删除的答案完全相同的查询我不喜欢这两个标量子查询,但 mysql 似乎以神秘的方式工作......
  • 是的,@Guerra 也是这样。我不知道他为什么删除帖子,因为查询比问题的性能更好;他应该解释一下别名不会降低性能。
  • 他有个奇怪的理论,就是使用关联名/别名导致查询结果慢。
  • 但他只需要承担错误并保留代码。没什么大不了的。
  • 感谢您的建议。 mysql会对此进行优化以仅运行两个子查询一次吗?没什么大不了的,只是想知道。
【解决方案3】:

这只是一个建议;不知道行不行。

您的查询的问题是您在两列上有不等式。这使得对它们都使用索引变得非常困难——这反过来又使join 非常低效。这个想法是做两个连接,一个用于不等式的每一侧,然后在on 条件中包含id。因此,只有通过两者的节点才会通过:

UPDATE node a JOIN 
      (SELECT lft, rgt
       FROM node
       WHERE id = ? 
      ) l
      ON a.lft <= l.lft  join
      (SELECT lft, rgt
       FROM node
       WHERE id = ? 
      ) r
      on a.rgt >= r.rgt
    SET a.count = a.count + 1 ; 

正如我所说,我不知道这是否可行。但是您应该能够轻松地检查查询的explain,以查看计划是否使用索引来处理这两个不等式。

【讨论】:

  • l.id = r.id 有必要吗?因为你已经把它们等于?
  • @LucasHarada 。 . .我想你是对的。这两个子查询应该得到相同的行。我只是想把它分成两个显式连接,这样两个索引都可以使用。
【解决方案4】:

我知道 mysql 在引用正在更新的表时存在问题,但对我来说显而易见的解决方案是:

update node  A 
set A.count=A.count+1
WHERE EXISTS (
   SELECT *
   FROM node B 
   WHERE B.id=?
   AND A.lft<=B.lft and A.rgt>=B.rgt
   );

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    • 2012-06-13
    • 2020-03-19
    • 2014-03-12
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多