使用限制加速大型MySQL查询

使用限制加速大型MySQL查询,mysql,Mysql,我的SQL有问题,响应速度慢 这不是因为我缺少索引,而是因为结果是巨大的。我使用这些来提供一个API响应,并将其传递给外部客户机。为了使响应易于管理,我使用分页。最终,大型页面最终会减速到需要800秒才能完成的程度。这意味着web服务等待响应的时间很长 mysql> EXPLAIN SELECT * FROM externallinks_global LEFT JOIN externallinks_paywall ON externallinks_global.paywall_id=ext

我的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));


全局表大约有2700万行,paywall表大约有1600万行。

如果您使用的是偏移量1000000,MySQL必须在内部生成,然后扔掉结果的前100万行,只发送1000行。对于偏移量为1001000的下一页,它必须再次生成这100万(加上1000)行。这显然会花费越来越多的时间,在第1000页上,MySQL将读取第1页的行1000次,并丢弃它们999次

首先要确保使用的是“服务器端分页”而不是“客户端分页”。这是客户端环境(在本例中是api)中的连接设置。虽然名称中还包含单词“page”(因为它是一个类似的概念),但它不仅用于您的分页类型,而且您通常应该启用它(通常在默认情况下是启用的)

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

如果您没有选择(您应该检查两次或十次,然后再忽略它,因为您第一次没有让它工作),您可以优化您的查询。由于您一个接一个地检索页面,并且结果中的每一行都有其唯一的
url\u id
,因此您可以使用上次读取的
url\u 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;
用上一页的最后一个
url\u ID
替换
:最后一个读取的\u ID
。必须在api中管理该值,就像当前存储偏移量一样

请注意订购人。不使用它会给您带来不一致和意外的结果,因为如果您不设置顺序,MySQL可以在下一页中随机选择另一个,您可能会多次得到行,或者根本没有

你必须考虑一些事情:

  • 您不能跳转到任何随机页面(因为您需要知道上次收到的
    url\u id
    ),但因为您正在按顺序获取数据,所以这没有问题
  • 如果在读取时进行了更改(例如,如果不使用事务),则将获得尚未读取行的更新数据。不过,这将比您当前的方法安全得多:如果您当前将第一行的
    存档从
    0
    更改为
    1
    (阅读后),使用
    offset
    也将包括第一行,并且您将获得两次某些行
您应该测试使用主键或
是否有存档,id
会更快。它将取决于具有
has_archive=1
的行的百分比,如果为20%,主键可能会超过其他索引。通过强制索引来测试它,例如

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


如果您在不使用
force
的情况下进行测试,它可能会使用索引
hasarchive

,可能有(也可能没有)方法来改进您的查询、数据/数据准备或设置,但我们只知道索引的名称。这就像“我有一辆车。它是由很多部件组成的,我将其中一个命名为‘api13’(虽然不确定它是否真的是有用的部件)。这辆车会发出奇怪的噪音,而且驾驶性能不好。我该怎么办?”没有更多的细节,我们只能给你非常一般的提示,所以请尝试一下,例如,以及那里的所有链接。谢谢你的评论。您还需要哪些其他数据?桌子本身?表大小?@Solarflare我添加了更多细节。如果您需要更多详细信息,请让我知道。除了表详细信息之外:重要的部分是查询本身(您可以简化它,例如,如果它不改变查询的工作方式(连接、位置和顺序),则删除私有信息)。还有一些关于您的数据/结果的详细信息(例如:“通常的查询将找到2700万行中的4%,表1的每一行与表2的大约5行合并”),以及例如,如果您的数据是静态的,如果数据只是附加的,或者可以到处更改。如果查询比较复杂,可以使用一些示例数据来帮助理解查询。在我们看到您的查询后,可能会出现其他问题。@Solarflare很抱歉延迟回复。我一直很忙。我使用的查询就是上面解释的,有限制。我不确定这是否是你想要的。如解释中所示,它有2700万行中的4291236行。这是一张多对一的桌子。因此,联接在左侧有多行,与右侧的一行相匹配。如果你需要更多,请告诉我。我的SQL技能充其量只是中等水平,所以我在这里学到了很多。很抱歉回复太晚。我实现了您的建议,通过重新执行查询和API如何分页
... FROM externallinks_global force index (primary) left join ...
... FROM externallinks_global force index (hasarchive) left join ...