次の表があります。
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.
このクエリは実行に長い時間がかかります、改善するためのアイデアはありますか?
これが元のクエリです:
_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)
;
_
クエリをリファクタリングする理由は次のとおりです。
インラインテーブルua
を見ると、LIMIT
を使用して30行だけを取得しています。 これは、_user_actions
_テーブルがどんなに大きくても発生します。 _ORDER BY timestamp DESC
_がLIMIT
の前に発生するため、すでに注文されています。
インラインテーブルu
を見ると、id
、_Twitter_username
_、email
があります。 id
は、結合を実装するために必要です。
2つの理由により、_LEFT JOIN
_ではなく_INNER JOIN
_を使用しています。
ua
に基づいてクエリの順序を保持するua
のuser_idがusers
テーブルに存在しない場合に備えて、すべてのユーザーアクションを表示します。これらを実行すると、一時テーブルが強制的に小さくなります。それでも、一時テーブルがディスクに配置されるようにするには、 @ DTestの回答の箇条書き#3を実装 が必要です。
主な問題は、クエリにはフィルタリングがない(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)の量によっては、パフォーマンスに影響を与える可能性があります。