我遇到了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万行。
答案 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_archive
到0
的第一行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
。