2017-06-21 9 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)); 

グローバルテーブルは、およそ2700万行を持っており、ペイウォールテーブルを約があります。ここでは

は、クエリが上で実行されていると、テーブルがそれに参加した表であります1600万

+0

クエリ、データ/データの作成、またはセットアップを改善する方法があるかもしれませんが、わかっているのはインデックスの名前だけです。それは「私は車を持っています。それは多くの部品から作られています。私はこれらの「api13」の1つに名前を付けました(実際には役に立つかどうかはわかりません)。車は騒音を発し、私は何をすべきか?"詳しくは、非常に一般的なヒントを提供するだけです。 [MySQLデータ - ページングを実装する最善の方法?](https://stackoverflow.com/questions/3799193/mysql-data-best-way-to-implement-paging)とそのすべてのリンク – Solarflare

+0

ご意見ありがとうございます。どのような追加データを探していますか?テーブル自体?テーブルサイズ? – Cyberpower678

+0

@Solarflare詳細を追加しました。詳細が必要な場合は、私にお知らせください。 – Cyberpower678

答えて

0

offset 1000000を使用している場合、MySQLは内部的に生成し、1000行を送信するために結果の最初の100万行をスローしなければなりません。 offset 1001000の次のページでは、これらの100万(プラス1000)行を再度生成する必要があります。これは明らかにますます時間が掛かり、1000ページでは、MySQLは1ページ目の行を1000回読み、999回も破棄します。

まず、「クライアント側のページング」ではなく「サーバー側のページング」を使用していることを確認してください。これはクライアントの環境(この場合はAPI)の接続設定です。名前には "page"という単語も含まれていますが(これは似たような概念なので)、あなたのページングに使用されるだけでなく、通常は有効にする必要があります。

これは実際にはすでに問題を解決している可能性があります。つまり、apiは結果セット全体のリクエストをサーバーに送信し、行ごとにフェッチしてクライアントに渡します。キャッシングはサーバー上で行われるため、結果セット全体(「クライアントサイドページング」)を取得するか、API内に複数の行を格納する必要はありません。

これはオプションではありません(最初に動作しないため無視する前に2回または10回確認する必要があります)ので、クエリを最適化できます。ページを順に検索して結果のすべての行に一意のurl_idがある場合は、最後に読み取ったurl_idを使用して、前の行を読み込んだり破棄したりするのではなく、実際にすべてスキップします。これは単なるインデックス検索を必要とし、すべてのページはほぼ同じ時間がかかります。だからあなたの前のページの最後のurl_id:LAST_READ_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; 

を試してみてください。オフセットを保存しているのと同じように、APIの値を管理する必要があります。

order byにお気づきください。注文を設定しないと、MySQLが次のページのために別のものをランダムに決定することが許可されているので、行を数回取得するか、まったく取得しない可能性があるため、

あなたはいくつかのことを考慮する必要があります:

  • あなたが任意のランダムなページにジャンプすることはできません(あなたが最後にurl_idを受け取っ知っておく必要があるため)、しかし、あなたが順番にあなたのデータを取得しているので、これは問題ありません
  • (トランザクションを使用しないなど)読んでいる間に変更すると、未読の行の更新データが取得されます。あなたの現在の方法よりもはるかに安全です。最初の行のhas_archive0から1(読んだ後)に変更すると、offsetを使用すると、最初の行も追加され、2度の行が表示されます。

プライマリキーを使用するか、または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を使用します。

+0

非常に遅れて申し訳ありません。私は、あなたの提案を実装し、クエリをやり直し、APIがどのようにページを張るか、そしてWOWの違いを実装しました。助けてくれてありがとう。 – Cyberpower678

関連する問題