使用LIMIT

时间:2017-06-21 21:04:13

标签: mysql

我遇到了SQL问题,响应时间也很慢。

这不是因为我缺少索引,而是因为结果是巨大的。我正在使用这些来提供API响应,并将其传递给外部客户端。为了使响应易于管理,我正在使用分页。最终,大页面最终会减慢到需要800秒才能完成的程度。这意味着Web服务长期等待提供响应。

mysql> EXPLAIN SELECT * FROM externallinks_global LEFT JOIN externallinks_paywall ON externallinks_global.paywall_id=externallinks_paywall.paywall_id WHERE `has_archive` = 1 LIMIT 1385000,1001;
+------+-------------+-----------------------+--------+------------------------------------------------------------------+------------+---------+--------------------------------------------------+---------+-------+
| id   | select_type | table                 | type   | possible_keys                                                    | key        | key_len | ref                                              | rows    | Extra |
+------+-------------+-----------------------+--------+------------------------------------------------------------------+------------+---------+--------------------------------------------------+---------+-------+
|    1 | SIMPLE      | externallinks_global  | ref    | HASARCHIVE,APIINDEX7,APIINDEX10,APIINDEX11,APIINDEX12,APIINDEX13 | APIINDEX13 | 1       | const                                            | 4291236 |       |
|    1 | SIMPLE      | externallinks_paywall | eq_ref | PRIMARY                                                          | PRIMARY    | 4       | s51059__cyberbot.externallinks_global.paywall_id |       1 |       |
+------+-------------+-----------------------+--------+------------------------------------------------------------------+------------+---------+--------------------------------------------------+---------+-------+
2 rows in set (0.01 sec)

以上是正在解释的问题查询。这需要800秒才能执行。可以看出它的索引非常好。我的问题是,当在大集合中深入获取结果块时,如何才能立即获得结果?有没有办法做到这一点?

以下是运行查询的表和加入它的表:

CREATE TABLE IF NOT EXISTS `externallinks_global` (
                              `url_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
                              `paywall_id` INT UNSIGNED NOT NULL,
                              `url` VARCHAR(767) NOT NULL,
                              `archive_url` BLOB NULL,
                              `has_archive` TINYINT UNSIGNED NOT NULL DEFAULT '0',
                              `live_state` TINYINT UNSIGNED NOT NULL DEFAULT '4',
                              `last_deadCheck` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
                              `archivable` TINYINT UNSIGNED NOT NULL DEFAULT '1',
                              `archived` TINYINT UNSIGNED NOT NULL DEFAULT '2',
                              `archive_failure` BLOB NULL DEFAULT NULL,
                              `access_time` TIMESTAMP NOT NULL,
                              `archive_time` TIMESTAMP NULL DEFAULT NULL,
                              `reviewed` TINYINT UNSIGNED NOT NULL DEFAULT '0',
                              PRIMARY KEY (`url_id` ASC),
                              UNIQUE INDEX `url_UNIQUE` (`url` ASC),
                              INDEX `LIVE_STATE` (`live_state` ASC),
                              INDEX `LAST_DEADCHECK` (`last_deadCheck` ASC),
                              INDEX `PAYWALLID` (`paywall_id` ASC),
                              INDEX `REVIEWED` (`reviewed` ASC),
                              INDEX `HASARCHIVE` (`has_archive` ASC),
                              INDEX `ISARCHIVED` (`archived` ASC),
                              INDEX `APIINDEX1` (`live_state` ASC, `paywall_id` ASC),
                              INDEX `APIINDEX2` (`live_state` ASC, `paywall_id` ASC, `archived` ASC),
                              INDEX `APIINDEX3` (`live_state` ASC, `paywall_id` ASC, `reviewed` ASC),
                              INDEX `APIINDEX4` (`live_state` ASC, `archived` ASC),
                              INDEX `APIINDEX5` (`live_state` ASC, `reviewed` ASC),
                              INDEX `APIINDEX6` (`archived` ASC, `reviewed` ASC),
                              INDEX `APIINDEX7` (`has_archive` ASC, `paywall_id` ASC),
                              INDEX `APIINDEX8` (`paywall_id` ASC, `archived` ASC),
                              INDEX `APIINDEX9` (`paywall_id` ASC, `reviewed` ASC),
                              INDEX `APIINDEX10` (`has_archive` ASC, `live_state` ASC, `paywall_id` ASC, `archived` ASC, `reviewed` ASC),
                              INDEX `APIINDEX11` (`has_archive` ASC, `archived` ASC, `reviewed` ASC),
                              INDEX `APIINDEX12` (`has_archive` ASC, `live_state` ASC, `paywall_id` ASC),
                              INDEX `APIINDEX13` (`has_archive` ASC, `live_state` ASC),
                              INDEX `APIINDEX14` (`has_archive` ASC, `live_state` ASC, `paywall_id` ASC, `reviewed` ASC),
                              INDEX `APIINDEX15` (`has_archive` ASC, `live_state` ASC, `reviewed` ASC),
                              INDEX `APIINDEX16` (`has_archive` ASC, `paywall_id` ASC, `reviewed` ASC));

CREATE TABLE IF NOT EXISTS `externallinks_paywall` (
                              `paywall_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
                              `domain` VARCHAR(255) NOT NULL,
                              `paywall_status` TINYINT UNSIGNED NOT NULL DEFAULT 0,
                              PRIMARY KEY (`paywall_id` ASC),
                              UNIQUE INDEX `domain_UNIQUE` (`domain` ASC),
                              INDEX `PAYWALLSTATUS` (`paywall_status` ASC));

全局表大约有2700万行,而付费专区表大约有1600万行。

1 个答案:

答案 0 :(得分:0)

如果您使用的是offset 1000000,MySQL必须在内部生成并丢弃结果的前100万行,以便向您发送1000行。对于offset 1001000的下一页,它必须再次生成这100万(加1000)行。这显然会花费越来越多的时间,而在第1000页,MySQL将会读取第1页的行数千次,并将它们丢弃999次。

首先要确保你正在使用"服务器端分页"而不是"客户端分页"。这是客户端环境中的连接设置(在本例中为您的api)。虽然这个名字还包括" page" (因为它是一个类似的概念),它不仅用于你的那种分页,你通常应该启用它(通常它默认启用)。

这实际上可能已经解决了您的问题:您的api会将整个结果集的请求发送到服务器,然后逐行获取并将其传递给客户端。缓存是在服务器上完成的,因此您无需检索整个结果集("客户端分页")或在api中存储多行。

如果您没有选择(您应该在忽略它之前检查它两次或十次,因为您没有让它第一次工作),您可以优化您的查询。由于您一个接一个地检索页面,并且结果中的每一行都有唯一的url_id,因此您可以使用上次读取url_id来跳过所有先前的行而不是读取和丢弃它们。这只需要单个索引查找,每个页面将花费大约相同的时间。所以试试

 SELECT * FROM externallinks_global 
 LEFT JOIN externallinks_paywall 
 ON externallinks_global.paywall_id=externallinks_paywall.paywall_id 
 WHERE url_id > :LAST_READ_ID and has_archive = 1 
 ORDER BY url_id
 LIMIT 1000;

:LAST_READ_ID替换为您上一页的最后一个url_id。您必须在api中管理该值,与当前存储偏移量相同。

请注意order by。不使用它会给你带来不一致和意想不到的结果,因为如果你没有设置订单,MySQL可以随机决定下一页的不同,你可能会多次获取行或根本不行。

你必须考虑一些事情:

  • 你不能跳转到任何随机页面(因为你需要知道最后收到的url_id),但由于你按顺序获取数据,这不是问题
  • 如果您在阅读时更改(例如,如果您不使用交易),您将获得尚未读取的行的更新数据。它会比你目前的方法更安全:如果你现在会改变,例如从has_archive0的第一行1(在您阅读之后),使用offset现在也将包含第一行,您将获得两次行。

您应该测试是否使用主键或has_archive,id会更快。它将取决于具有has_archive = 1的行的百分比,而对于20%,主键可能会超过其他索引。通过强制索引来测试它,例如

... FROM externallinks_global force index (primary) left join ...

... FROM externallinks_global force index (hasarchive) left join ...

如果不使用force进行测试,则可能会使用索引hasarchive