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