优化包含 WHERE 和 ORDER BY 的 MySQL UPDATE 查询?

Posted

技术标签:

【中文标题】优化包含 WHERE 和 ORDER BY 的 MySQL UPDATE 查询?【英文标题】:Optimize MySQL UPDATE query that contains WHERE and ORDER BY? 【发布时间】:2013-11-12 22:00:07 【问题描述】:

如何优化此查询?如果我在没有 ORDER BY 子句的情况下运行它,它会在

UPDATE companies
SET
    crawling = 1
WHERE
    crawling = 0
    AND url_host IS NOT NULL
ORDER BY
    last_crawled ASC
LIMIT 1;

如果我将此查询作为 SELECT 运行,它也很快 (

SELECT id
FROM companies
WHERE
    crawling = 0
    AND url_host IS NOT NULL
ORDER BY
    last_crawled ASC
LIMIT 1;

这是我的表架构:

CREATE TABLE `companies` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `url` varchar(255) DEFAULT NULL,
  `url_scheme` varchar(10) DEFAULT NULL,
  `url_host` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `crawl` tinyint(1) unsigned NOT NULL DEFAULT '1',
  `crawling` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `last_crawled` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`),
  KEY `url_host` (`url_host`),
  KEY `crawl` (`crawl`),
  KEY `crawling` (`crawling`),
  KEY `last_crawled` (`last_crawled`),
  KEY `url_scheme` (`url_scheme`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

更新一个

这个查询给了我以下错误:You can't specify target table 'companies' for update in FROM clause

UPDATE companies
SET crawling = 1
WHERE id = (
    SELECT id
    FROM companies
    WHERE
        crawling = 0
        AND url_host IS NOT NULL
    ORDER BY
        last_crawled ASC
    LIMIT 1
);

这个查询给了我以下错误:This version of mysql doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'

UPDATE companies
SET crawling = 1
WHERE id in (
    SELECT id
    FROM companies
    WHERE
        crawling = 0
        AND url_host IS NOT NULL
    ORDER BY
        last_crawled ASC
    LIMIT 1
);

【问题讨论】:

无订单和无订单的执行计划是什么样的?看起来 last_Crawled 上的索引可能丢失了。 你有关于 last_crawled 的索引吗?看到选择很快,只需抓住 id 并在更新中使用它,这是显而易见的解决方法 @xQbert & @Tony Hopkinson - last_crawled 上有一个索引。但是,没有复合索引。也许需要一个。 @Tony Hopkinson - 我正在使用此查询来选择下一家要抓取的公司,因此我无法将 SELECT 和 UPDATE 查询分开,否则请求下一家公司的其他流程将重叠。 @T.BrianJones 这就是为什么我想查看执行计划以了解是否在 order by 上使用了全表扫描或者是否正在命中索引:P 【参考方案1】:

尽量不要对如此少量的更新使用 ORDER-BY 和 LIMIT。

    UPDATE companies t1
    join
    (
        SELECT c.id,@RowNum:=@RowNum+1 AS RowID
        FROM companies c, (SELECT @RowNum := 0)r
        WHERE c.crawling = 0 AND c.url_host IS NOT NULL
        ORDER BY c.last_crawled ASC
    )t2
    ON t2.RowID=1 AND t1.id=t2.id
    SET t1.crawling = 1

编辑:1

确保您的索引位于 (last_crawled ASC , id ASC)

    UPDATE companies t1
    join
    (
        Select ID,RowID
        From
        (
            SELECT c.id,@RowNum:=@RowNum+1 AS RowID
            FROM companies c, (SELECT @RowNum := 0)r
            WHERE c.crawling = 0 AND c.url_host IS NOT NULL
            ORDER BY c.last_crawled ASC
        )t2
        WHERE ROWID=1
    )t3
    ON t1.id=t3.id
    SET t1.crawling = 1

【讨论】:

这可行,但要慢得多( >30s )。如果这有所作为,company 表有数百万行。我不熟悉您在此查询中使用的某些语法(例如 @RowNum:=@RowNum+1 ),所以很遗憾,我无法在其上制作任何其他受过教育的 cmets。 在子查询中我分配行号,然后只选择前 1 个。尝试更新的查询。 即使你有索引,性能也很大程度上取决于列爬取的基数。可能的值似乎是 0 和 1,因此您可以想象该列不是在 where 条件下使用该列对表执行更新的最佳选择。但如果我们没有其他任何东西,我想这是我们能做的最好的事情。

以上是关于优化包含 WHERE 和 ORDER BY 的 MySQL UPDATE 查询?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL优化:order by和limit

优化 MySQL - WHERE 和 ORDER BY 使用不同的列

使用 JOIN 优化 SQL 查询的 ORDER BY 和 WHERE

使用 WHERE、GROUP BY 和 ORDER BY

oracle用WHERE替代ORDER BY

Order by