2017-06-21 70 views
0

我遇到了SQL問題和響應時間慢的問題。使用LIMIT加速大型MySQL查詢

這不是因爲我缺少索引,而是因爲結果很大。我使用這些來提供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)); 

全局表具有約佔27萬行,而付費牆表有大約1600萬。

+0

有可能(或可能不會)改善您的查詢,數據/數據補充或您的設置,但我們知道的只是索引的名稱。這就像「我有一輛汽車,它是由很多零件組成的,我將其中一個'api13'命名爲'api13'(不確定它是否真的是一個有用的零件)。汽車發出奇怪的噪音,不開車。我該怎麼辦?」沒有更多的細節,我們只能給你非常一般的提示,所以請嘗試[MySQL數據 - 實現分頁的最佳方式?](https://stackoverflow.com/questions/3799193/mysql-data-best-way-to-implement-paging)以及所有鏈接。 – Solarflare

+0

感謝您的評論。你在尋找什麼額外的數據?桌子本身?桌子大小? – Cyberpower678

+0

@Solarflare我添加了更多細節。如果你需要更多的細節,請讓我知道。 – Cyberpower678

回答

0

如果您使用的是offset 1000000,MySQL必須在內部生成並丟棄前100萬行結果,以便向您發送1000行。對於offset 1001000的下一頁,它將不得不再次生成這100萬行(再加上1000行)。這顯然需要越來越多的時間,並且在第1000頁上,MySQL將讀取第1頁的行數千次,並丟棄它們999次。

首先您應該確定您正在使用「服務器端分頁」而不是「客戶端分頁」。這是您客戶端環境中的連接設置(在本例中爲api)。雖然名稱中還包含「page」(因爲它是一個類似的概念),但它不僅用於您的類型的分頁,而且通常應該啓用它(通常默認情況下啓用它)。

這實際上可能已經解決了您的問題:您的api會將整個結果集的請求發送到服務器,然後逐行讀取並傳遞給客戶端。緩存是在服務器上完成的,因此您不需要檢索整個結果集(「客戶端分頁」)或在api中存儲多行。

如果這對您沒有選擇(您應該先檢查兩次或10次,但在忽略它之前,因爲您沒有在第一次使用它),您可以優化您的查詢。由於您一個接一個地檢索頁面,並且結果中的每一行都有其唯一的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中管理該值,與您當前存儲偏移量相同。請注意0​​。不使用它會給你帶來不一致的和意想不到的結果,因爲如果你沒有設置一個命令,MySQL允許爲下一頁隨機選擇一個不同的頁面,並且你可能會多次讀取行或者根本不讀取。

你必須要考慮一些事情:

  • 你不能妄下任何隨機頁面(因爲你需要知道最後收到url_id),但因爲你是爲了讓你的數據,這是沒有問題
  • 如果您在閱讀時發生更改(例如,如果您未使用事務),您將獲得尚未讀取行的更新數據。然而,它比現在的方法更安全:如果你現在會改變has_archive01的第一行(在您閱讀之後),使用offset現在也會包含第一行,您將獲得一些行兩次。

您應該測試使用主鍵還是has_archive,id會更快。這取決於具有has_archive = 1的行的百分比,並且具有20%的主鍵可能會擊敗另一個索引。通過強制索引來測試它,例如不使用force

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

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

如果你測試,它可能會使用索引hasarchive

+0

對不起,遲到的迴應。我實現了你的建議,重做了查詢以及API分頁,WOW有什麼不同。非常感謝你的幫助。 – Cyberpower678