web-dev-qa-db-ja.com

MySql-このクエリを高速化する方法

次の表があります。

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `first_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `last_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `account_data` text COLLATE utf8_unicode_ci,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `Twitter_username` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `crypted_password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `password_salt` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `persistence_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `single_access_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `perishable_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `login_count` int(11) NOT NULL DEFAULT '0',
  `failed_login_count` int(11) NOT NULL DEFAULT '0',
  `last_request_at` datetime DEFAULT NULL,
  `current_login_at` datetime DEFAULT NULL,
  `last_login_at` datetime DEFAULT NULL,
  `current_login_ip` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `last_login_ip` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `is_admin` tinyint(1) DEFAULT '0',
  `referrer_id` int(11) DEFAULT NULL,
  `partner` tinyint(1) DEFAULT '0',
  `subscription_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT 'free',
  `workflow_state` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `persona_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `persona_index` (`persona_id`)
) ENGINE=InnoDB 

そしてテーブル:

CREATE TABLE `user_actions` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `action_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `module` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `data` text COLLATE utf8_unicode_ci,
  `timestamp` datetime DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id_index` (`user_id`),
  KEY `action_type_index` (`action_type`),
  KEY `user_action_type_index` (`user_id`,`action_type`),
  KEY `timestamp_index` (`timestamp`),
  KEY `user_id_timestamp_index` (`user_id`,`timestamp`)
) ENGINE=InnoDB 

問題は次のクエリにあります。

    SELECT user_actions.*, users.Twitter_username, users.email FROM `user_actions` 
INNER JOIN users ON (user_actions.user_id=users.id) ORDER BY timestamp DESC LIMIT 0, 30

ここに説明があります:

user_actions    
The table was retrieved with this index: user_id_timestamp_index
You can speed up this query by querying only fields that are within the index. Or you can create an index that includes every field in your query, including the primary key.
Approximately 76 rows of this table were scanned.
users   
This table was retrieved with a full table scan, which is often quite bad for performance, unless you only retrieve a few rows.
The table was retrieved with this index:
No index was used in this part of the query.
A temporary table was created to access this part of the query, which can cause poor performance. This typically happens if the query contains GROUP BY and ORDER BY clauses that list columns differently.
MySQL had to do an extra pass to retrieve the rows in sorted order, which is a cause of poor performance but sometimes unavoidable.
You can speed up this query by querying only fields that are within the index. Or you can create an index that includes every field in your query, including the primary key.
Approximately 3445 rows of this table were scanned.

このクエリは実行に長い時間がかかります、改善するためのアイデアはありますか?

4
Ran

これが元のクエリです:

_SELECT
    user_actions.*,
    users.Twitter_username,
    users.email
FROM
    `user_actions`  
    INNER JOIN users
    ON (user_actions.user_id=users.id)
    ORDER BY timestamp
    DESC LIMIT 0, 30
;
_

最初に気付くのは、2つのテーブル全体を結合していることです。 emailテーブルの_Twitter_username_とusersのみが必要なので、usersからの結合は、3つの列id、_Twitter_username_およびemail

2つ目はLIMIT句です。結合後に実行されています。結合の前に実行する必要があります。あなたのケースでは、最新の30のユーザーアクションをリクエストしています。 _user_actions_から30行のみが取得されることが保証できる場合、結合ははるかに速く動作するはずです。

@ DTest からの回答を読んだ場合、彼の最初の2つの箇条書きは、アクションmysqlのためにクエリの何が問題であるかをすでに示しています各テーブルからデータを収集します。重要なのは、クエリの処理中に一時テーブルがどのようになり、データがどこにあるか(メモリまたはディスク)を理解することです。

MySQLクエリオプティマイザーをだますためにクエリをリファクタリングする必要があります。クエリがより小さな一時テーブルを生成するように強制します。ほとんどの場合、my.cnfの構成を変更すると、劇的な違いが生じるはずです。このような他のケースでは、クエリのリファクタリングで十分な場合があります。

これは、より速く動作するはずのクエリに対する私の提案された変更です:

_SELECT
    ua.*,
    u.Twitter_username,
    u.email
FROM
    (SELECT * FROM `user_actions`
    ORDER BY timestamp DESC LIMIT 30) ua
    LEFT JOIN
    (SELECT id,Twitter_username,email FROM `users`) u
    ON (ua.user_id=u.id)
;
_

クエリをリファクタリングする理由は次のとおりです。

理由#1

インラインテーブルuaを見ると、LIMITを使用して30行だけを取得しています。 これは、_user_actions_テーブルがどんなに大きくても発生します。 _ORDER BY timestamp DESC_がLIMITの前に発生するため、すでに注文されています。

理由#2

インラインテーブルuを見ると、id、_Twitter_username_、emailがあります。 idは、結合を実装するために必要です。

理由#3

2つの理由により、_LEFT JOIN_ではなく_INNER JOIN_を使用しています。

  1. uaに基づいてクエリの順序を保持する
  2. uaのuser_idがusersテーブルに存在しない場合に備えて、すべてのユーザーアクションを表示します。

これらを実行すると、一時テーブルが強制的に小さくなります。それでも、一時テーブルがディスクに配置されるようにするには、 @ DTestの回答の箇条書き#3を実装 が必要です。

4
RolandoMySQLDBA

主な問題は、クエリにはフィルタリングがない(WHEREステートメントがない)ため、列_user_actions.*, Twitter_username, email_を持つすべての行を一時テーブルに配置して並べ替えを行うことです。

したがって、私が最初にすることは、結果セットに入れる行の数を制限することです。たとえば、WHERE timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)を追加して、過去7日以内の結果のみを取得するとします(ユースケースで許容できる場合)。

次に、_user_actions_から必要な列のみをプルするようにクエリを変更して、一時テーブルに入れるために必要な情報の量を減らします。

ソートするために一時テーブルに配置する必要のある行/列を削除した場合とそうでない場合があるので、MySQLが一時テーブルを処理する方法を見てみましょう。 _tmp_table_size_ 変数に関するドキュメントから(強調を追加):

内部メモリ内一時テーブルの最大サイズ。 (実際の制限は、tmp_table_sizeとmax_heap_table_sizeの最小値として決定されます。)1メモリ内の一時テーブルが制限を超えると、MySQLはそれを自動的にディスク上のMyISAMテーブルに変換します。

最初に、上付き文字によって表される警告を指摘しましょう 1:メモリ内に作成される一時テーブルのサイズは、_tmp_table_size_または_max_heap_table_size_の最小値であるため、1つを増やす場合は、必ずもう1つを増やしてください。

データの量がこれら2つの変数の最小サイズを超える場合、データはディスクに配置されます。ディスクが遅い。回避できる場合は、ディスクを使用しないでください。

要点をまとめると:

  • WHEREを使用して、ソートする行の数を制限します。 LIMITを実行している場合でも、すべての行が一時テーブルに配置されてソートされています。

  • 要求する列の数を制限します。それらが必要ない場合は、要求しないでください。

  • 最後の手段として、クエリが _tmp_table_size_ ステータス変数を増やしている場合は、_max_heap_table_size_および_Created_tmp_disk_tables_のサイズを増やします。また、これを大幅に増やしないでください。ご使用のハードウェアと、サーバー上にあるRAM)の量によっては、パフォーマンスに影響を与える可能性があります。

3
Derek Downey