【问题标题】:Super slow query with CROSS JOIN使用 CROSS JOIN 的超慢查询
【发布时间】:2011-09-27 20:29:28
【问题描述】:

我有两个名为 table_1 (1GB) 和 reference (250Mb) 的表。

当我在引用上查询交叉连接时,需要 16 小时才能更新 table_1 .. 我们为 XFS 更改了系统文件 EXT3,但仍然需要 16 小时.. 我做错了什么??

这里是更新/交叉连接查询:

  mysql> UPDATE table_1 CROSS JOIN reference ON
  -> (table_1.start >= reference.txStart AND table_1.end <= reference.txEnd)
  -> SET table_1.name = reference.name;
  Query OK, 17311434 rows affected (16 hours 36 min 48.62 sec)
  Rows matched: 17311434  Changed: 17311434  Warnings: 0

这是 table_1 的 show create table 和参考:

    CREATE TABLE `table_1` (
     `strand` char(1) DEFAULT NULL,
     `chr` varchar(10) DEFAULT NULL,
     `start` int(11) DEFAULT NULL,
     `end` int(11) DEFAULT NULL,
     `name` varchar(255) DEFAULT NULL,
     `name2` varchar(255) DEFAULT NULL,
     KEY `annot` (`start`,`end`)
   ) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;


   CREATE TABLE `reference` (
     `bin` smallint(5) unsigned NOT NULL,
     `name` varchar(255) NOT NULL,
     `chrom` varchar(255) NOT NULL,
     `strand` char(1) NOT NULL,
     `txStart` int(10) unsigned NOT NULL,
     `txEnd` int(10) unsigned NOT NULL,
     `cdsStart` int(10) unsigned NOT NULL,
     `cdsEnd` int(10) unsigned NOT NULL,
     `exonCount` int(10) unsigned NOT NULL,
     `exonStarts` longblob NOT NULL,
     `exonEnds` longblob NOT NULL,
     `score` int(11) DEFAULT NULL,
     `name2` varchar(255) NOT NULL,
     `cdsStartStat` enum('none','unk','incmpl','cmpl') NOT NULL,
     `cdsEndStat` enum('none','unk','incmpl','cmpl') NOT NULL,
     `exonFrames` longblob NOT NULL,
      KEY `chrom` (`chrom`,`bin`),
      KEY `name` (`name`),
      KEY `name2` (`name2`),
      KEY `annot` (`txStart`,`txEnd`)
   ) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;

【问题讨论】:

  • 表没有主键?
  • 你的问题标题中不需要 SHOUT ......
  • 我在问题标题中喊到哪里了??
  • 确实这些表没有主键,因为......我仍然想知道添加主键是否真的有帮助..

标签: mysql performance cross-join


【解决方案1】:

已经有人提议您添加一些索引。但我认为这两个索引可能会获得最佳性能:

ALTER TABLE `test`.`time` 
    ADD INDEX `reference_start_end` (`txStart` ASC, `txEnd` ASC),
    ADD INDEX `table_1_star_end` (`start` ASC, `end` ASC);

MySQL 查询只会使用其中一个,但 MySQL 会自动决定哪个更有用。

【讨论】:

    【解决方案2】:

    我发现 UPDATE 语句存在 2 个问题。

    End 字段没有索引。您拥有的复合索引 (annot) 将仅用于此查询中的 start 字段。您应该按照 Emre 的建议添加它们:

    ALTER TABLE `table_1` ADD INDEX ( `end` ) ;
    ALTER TABLE `reference` ADD INDEX ( `txEnd` ) ;
    

    其次,JOIN 可能(并且可能确实)找到与table_1 行相关的表reference 的许多行。因此,更新的table_1 的某些(或全部)行会更新很多次。检查此查询的结果,看看它是否与您更新的行数相同(17311434):

    SELECT COUNT(*)
    FROM table_1
      WHERE EXISTS
        ( SELECT *
          FROM reference
          WHERE table_1.start >= reference.txStart
            AND table_1.`end` <= reference.txEnd
        )
    

    可以有其他方法来编写此查询,但两个表上都缺少PRIMARY KEY 使得它更难。如果您在 table_1 上定义主键,请尝试此操作,将 id 替换为主键。

    更新:不,不要在有 34M 行的表上尝试。检查执行计划并首先尝试使用较小的表。

    UPDATE table_1 AS t1
      JOIN 
        ( SELECT t2.id
               , r.name
          FROM table_1 AS t2
            JOIN
              ( SELECT name, txStart, txEnd
                FROM reference
                GROUP BY txStart, txEnd
              ) AS r
              ON  t2.start >= r.txStart
              AND t2.`end` <= r.txEnd
          GROUP BY t2.id
        ) AS good
        ON good.id = t1.id
    SET t1.name = good.name;
    

    您可以通过在等效的 SELECT 上运行 EXPLAIN 来检查查询计划:

    EXPLAIN
    SELECT t1.id, t1.name, good.name
    FROM table_1 AS t1
      JOIN 
        ( SELECT t2.id
               , r.name
          FROM table_1 AS t2
            JOIN
              ( SELECT name, txStart, txEnd
                FROM reference
                GROUP BY txStart, txEnd
              ) AS r
              ON  t2.start >= r.txStart
              AND t2.`end` <= r.txEnd
          GROUP BY t2.id
        ) AS good
        ON good.id = t1.id ;
    

    【讨论】:

    • 哇非常感谢 :) 我在两个表上都添加了一个列 'id' auto_increment 主键。从 table_1 中,多行可以匹配 'reference' 中的相同名称(平均 800 行)。假设我在 good.id = t1.id 上运行 UPDATE JOIN --> 如果 table_1 中的 800 行与“reference”中的一个名称匹配,它将更新 table_1 中列名的 800 倍,对吗? :)
    • @madkitty:我的SELECT COUNT(*) FROM table_1 WHERE EXISTS ... 会返回什么?
    • @madkitty:你添加了两个end 索引吗?
    • 回答您的问题,如果 table_1 中的 800 行与“reference”中的一个名称匹配,则会更新 table_1 中的 这 800 行
    • 是的,我确实添加了两个末端索引,我运行了查询 SELECT COUNT(*) FROM table_1 WHERE EXISTS ( SELECT * FROM reference WHERE table_1.start >= reference.txStart AND table_1.end 我在 1 小时前运行了这个,它还在运行。
    【解决方案3】:

    交叉连接是笛卡尔积,它可能是计算成本最高的东西之一(它们不能很好地扩展)。

    对于i = 1到n的每个表T_i,交叉表T_1到T_n生成的行数是每个表的大小乘以其他表的大小,即

    |T_1| * |T_2| * ... * |T_n|

    假设每个表有 M 行,那么计算交叉连接的最终成本为

    M_1 * M_2 ... M_n = O(M^n)

    这是连接中涉及的表数量的指数。

    【讨论】:

    • 你说的是真的,但是在 MySQL 中a CROSS JOIN b ON join_condition 相当于a INNER JOIN b ON join_condition
    【解决方案4】:

    试试这个:

    UPDATE table_1 SET
    table_1.name = (
      select reference.name
      from reference
      where table_1.start >= reference.txStart
      and table_1.end <= reference.txEnd)
    

    【讨论】:

    • 你确定这是等价的吗?
    • 子查询甚至可能返回超过 1 行并引发错误。
    • 那么OP的原始查询也会失败,不是吗?
    • 我认为它不会失败,但会多次更新 table_1 行(每场比赛一次),这就是它速度慢的原因之一。
    • 虽然我的查询很慢,因为我们的数据是两个大数据。table_1 有 34994288 行,参考有 1560 行。也许设计是错误的..
    【解决方案5】:

    您应该索引table_1.startreference.txStarttable_1.endreference.txEnd 表字段:

    ALTER TABLE `table_1` ADD INDEX ( `start` ) ;
    ALTER TABLE `table_1` ADD INDEX ( `end` ) ;
    ALTER TABLE `reference` ADD INDEX ( `txStart` ) ;
    ALTER TABLE `reference` ADD INDEX ( `txEnd` ) ;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-04
      • 1970-01-01
      • 1970-01-01
      • 2015-11-03
      • 1970-01-01
      • 2015-03-16
      • 1970-01-01
      相关资源
      最近更新 更多