• 締切済み

MySQL IN演算子が遅い時の対策

topコマンドで見た時にいつもmysqldのCPU使用率が300前後になっています 改善方法など全くといっていいほどわからないのですが、まずslow-logを覗いてみたところ SELECT `a_id`, `b_id`, `hoge` FROM `table1` WHERE `a_id` IN (10,27,37,38,46,47,51,69,73,82,86,90,103,110,125,129,134,135,136,137,139,142,148,150,161,168,181,184,187,191,192,198,200,206,215,222,230,231,249,264,266,270,271,302,315,338,342,349,360,361,366,376,383,385,399,405,406,410,412,424,427,432,438,444,454,456,457,462,480,483,484,487,488,492,493,494,506,509,517,549,559,573,574,578,579,586,587,594,597,599,609,612,617,619,625,629,632,633,637,642,646,679,681,684,687,688,689,691,692,696,698,699,700,708,716,738,739,740,742 ,755,758,764,767,768,772,774,778,780,782,783,786,787,792,793,795,796,799,802,807,808,810,811,815,816,817,819,821,823,826,827,828,829,831,833,834,839,840,841,846,849,854,855,856,857,858,859,860,861,864,872,879,881,882,884,888,890,892,893,894,896,899,907) ORDER BY `updatetime` DESC LIMIT 0, 100; このようなクエリが1秒以上かかっていました。 `table1`と`table2`は`a_id`で紐付いていて、事前に`table2`に入っているa_idの一覧を取得してから上記のクエリを作成しています。 `table1`のスキーマを確認したところ INDEX a_id(`a_id`) INDEX updatetime(`updatetime`) となっていました。 (素人ながらに INDEX idx1(`a_id`, `updatetime`) とするべきだったのでは…と思ったのですがALTER TABLEで追加してみたところ、結果は変わりませんでした。) EXPLAINしてみたところ 1 | SIMPLE | feed | range | a_id,i1 | a_id | 5 | NULL | 237900 | Using where; Using filesort と返ってきました。 このような状態では、どのように対策するのが良いのでしょうか。 決まった正解はないのかもしれないですが、考えられる可能性で試すべき項目を教えて頂ければ幸いです。 ・table2のidに紐付いたtable1の情報を取り出したい場合の適切な設計 ・適切なSQL文 ・適切なインデックスの貼り方 などをご教授いただけると嬉しいです。 21時~0時くらいの時間帯になるとアクセスが増えload averageが10前後になってしまい、いつサーバーが落ちるかとヒヤヒヤしています… 是非ご回答宜しくお願い致します。

  • MySQL
  • 回答数5
  • ありがとう数5

みんなの回答

  • yambejp
  • ベストアンサー率51% (3827/7415)
回答No.5

苦労されているようでもうひと踏ん張りしてほしいところですが・・・ >INDEX a_id(`a_id`) されているのであれば、別解で上げているように select a_id,b_id,hoge from table1 force index(a_id) where a_id IN (・・・) order by updatetime desc limit 0,100 ; の処理で高速化が確認できませんか?

  • yambejp
  • ベストアンサー率51% (3827/7415)
回答No.4

>複合インデックスではなくそれぞれに設定するということでしょうか。 いえ複合です create table table1(a_id int ,b_id int,hoge varchar(20),updatetime datetime); insert into table1 values(1,1,'1','2013-08-27 00:00:01') ,(2,2,'2','2013-08-27 00:00:02') ,(3,3,'3','2013-08-27 00:00:03') ,(4,4,'4','2013-08-27 00:00:04') ,(5,5,'5','2013-08-27 00:00:05') ,(6,6,'6','2013-08-27 00:00:06') ,(7,7,'7','2013-08-27 00:00:07') ,(8,8,'8','2013-08-27 00:00:08') ,(9,9,'9','2013-08-27 00:00:09'); として、 explain select a_id,b_id,hoge from table1 where a_id IN (1,3,10,20) order by updatetime desc limit 0,100; すると、type=ALL,possible_keys=NULLのグダグダなSQLになります alter table table1 add index index1(a_id,b_id,hoge,updatetime); でインデックスを追加して、 explain select a_id,b_id,hoge from table1 where a_id IN (1,3,10,20) order by updatetime desc limit 0,100; すると、type=range,possible_keys=index1の結果が得られます ただし、今回のように絞り込みがa_idでのみ処理される場合はFORCE INDEXでの 絞り込みも有効です //元のindexをはずしておく alter table table1 drop index index1; alter table table1 add index index2(a_id); explain select a_id,b_id,hoge from table1 force index(index2) where a_id IN (1,3,10,20) order by updatetime desc limit 0,100 ; この場合a_idをインデックスに利用しているのがわかります。

jimascript
質問者

お礼

回答ありがとうございます。 ALTER TABLEを試してみたのですが、なぜか処理が終わらずしかもtopコマンドで見た時にmysqldのCPU使用率が2%くらいとなってしまい、サイト自体も開けなくなってしまいました。 テーブルには現在962362行のデータしか入っていません。 ALTER TABLEをした瞬間にmysqldのCPU使用率が2%前後になり何も仕事をしなくなってしまうのはなぜなのでしょうか。 ALTER TABLEを停止しようにも、mysqlを再起動しないとどうにもならない状態となり、stopするのにも非常に時間がかかります。 一度nginxをstopしてから試してみたのですが結果は同じでした。 今回の質問とはずれてきてしまうので、新しく質問を作り直したいと思います。

  • mitoneko
  • ベストアンサー率58% (469/798)
回答No.3

  No.1さんのSQL文のwhere句は、もっともっと長くしても全然かまわないんですよ。元のINの中のリストを作るのに必要な全部の条件をandで繋げてあげればそれで事足ります。  別解を作るなら、  SELECT `a_id`, `b_id`, `hoge`  FROM `table1`  WHERE `a_id` EXISTS (SELECT a_id FROM table2 WHERE お好きなだけ条件をつける)  ORDER BY `updatetime` DESC  LIMIT 0, 100;  でも良いです。  まぁ、どちらにせよ、今よりは、劇的に早くなるはずです。  インデックスはそっと置いておいてもね。  実は、両方のテーブルに、a_idのインデックスがあり、かつ、どちらかのテーブルにa_idの外部参照制約がついていれば、No1.さんのSQLの方が早いと思います。  このSQLの為だけのインデックス考慮なら、table2に条件に絡むインデックスと、table1にa_idとupdatetimeに単独カラムのインデックスでしょう。  ただでさえ、INの使用は気をつけないと、速度低下を招きますが・・・・今回の事例は、それに輪を掛けています。どれだけCPU資源を使えるかへのチャレンジと言われても仕方ないくらいにです。(これINの中身が増殖してきたらどうなるんでしょう?いったい、select文は何文字まで書いていいんでしょう?という問いへの実証SQLかもしれません。多分、どこかの時点で、いずれ、エラーで止まります。)

jimascript
質問者

お礼

そうだったのですね。 勉強不足でお恥ずかしい限りです… 教えていただいたようにEXISTSを使ってSQL文を作ってみたのですが、 WHERE `a_id` EXISTS (… ではなくて WHERE EXISTS (… `table1`.`a_id` = `table2`.`a_id` … で大丈夫でしょうか? EXISTSの前に`a_id`を書くとエラーとなってしまいうまくいきませんでした。 何はともあれidを羅列するSQL文じゃなくなってスッキリできました。 ありがとうございます。

  • yambejp
  • ベストアンサー率51% (3827/7415)
回答No.2

インデックスは a_id, b_id, hoge,updatetime

jimascript
質問者

補足

複合インデックスではなくそれぞれに設定するということでしょうか。

  • Picosoft
  • ベストアンサー率70% (274/391)
回答No.1

> `table1`と`table2`は`a_id`で紐付いていて、事前に`table2`に入っているa_idの一覧を取得してから上記のクエリを作成しています。 事前にtable2から一覧を取得しなくても、以下のSQLですべて事足ります。 SELECT T1.a_id, T1.b_id, T1.hoge FROM table1 T1, table2 T2 WHERE T1.a_id = T2.a_id ORDER BY T1.updatetime DESC LIMIT 0, 100

jimascript
質問者

補足

回答ありがとうございます。 SQL文ですが、table2のa_idを全て取り出しているわけではなく、WHEREで条件を指定しています。 説明不足で申し訳ございません。 引き続き宜しくお願い致します。

関連するQ&A

  • mysqlのインデックスについて質問です。

    mysqlのインデックスについて質問です。 http://archiva.jp/web/server-side/sql_02.html に、 『mysqlでは1つのクエリ実行で、1つのテーブルにつき1つのインデックスしか使用できない』 とあります。 よく以下のようなテーブル定義を見かけますが、 CREATE TABLE IF NOT EXISTS `data` ( `id` int(10) NOT NULL default '0', `user_id` int(10) NOT NULL default '0', `file_id` int(10) NOT NULL default '0', `name` text collate utf8_unicode_ci NOT NULL default '', PRIMARY KEY (`id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; idとuser_idを条件にするクエリであれば、上記制約を満たすため、 KEY `id_user_id_idx` (`id`,`user_id`) の複合クエリを定義すれば良いのでしょうか? また、idとuser_idとfile_idを条件にするクエリも考慮に入れた場合、 テーブル定義は以下のようにするのでしょうか? CREATE TABLE IF NOT EXISTS `data` ( `id` int(10) NOT NULL default '0', `user_id` int(10) NOT NULL default '0', `file_id` int(10) NOT NULL default '0', `name` text collate utf8_unicode_ci NOT NULL default '', PRIMARY KEY (`id`), KEY `user_id` (`user_id`), KEY `id_user_id_idx` (`id`,`user_id`), KEY `id_user_id_file_id_idx` (`id`,`user_id`,`file_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 最後に、create table 時のインデックスの指定は、 INDEX `user_id` (`user_id`), のようにもできますが、INDEXとKEYの違いは何かあるのでしょうか。

    • ベストアンサー
    • MySQL
  • 複数のindexについて

    CREATE TABLE `test` ( `id` INT(8) , `num` INT(8) , 他多数 (省略) ) このようなテーブルがある場合、以下の2種類のインデックスのつけ方にどのような違いがあるのでしょうか? phpmyadminで確認すると、(1)はインデックスが合体しており、(2)はインデックスが個別に分かれています。 (1) ALTER TABLE `test` ADD INDEX ( `id` , `num` ) (2) ALTER TABLE `test` ADD INDEX ( `id` )   ALTER TABLE `test` ADD INDEX ( `num` ) ちなみに、以下のようなSELECT文を用いる場合には、どちらのインデックスが適していますか? SELECT * FROM test WHERE id='●' and num > '△' ( mysql5,MyISAM )

    • ベストアンサー
    • MySQL
  • mysqlでSELECTの速度を上げる方法

    以下のようなSQLを発行すると、mysqlの処理時間が非常に多くかかるため、なんとか最適化を行いたいと考えています。 どのような方法があるのか教えていただけませんしょうか。 SELECT user_id,comment,comment_id,date,study_time,study,source FROM data_temp t1 WHERE NOT EXISTS (select comment_id from data t2 where t1.comment_id = t2.comment_id) ■補足 ・dataとdata_tempのテーブル構造は全く同じです。 ・SQLで実現したいことは、両テーブルのcomment_idをキーとして、dataに含まれないdata_tempの差分データを表示させたい。 なお、以下のインデックス作成は行いましたが、結果変わらずでした。 alter table data t1 ADD INDEX_t1 (user_id,comment,comment_id,date,study_time,study,source); alter table data_temp t1 ADD INDEX_t1 (user_id,comment,comment_id,date,study_time,study,source); よろしくお願いします。

    • ベストアンサー
    • MySQL
  • ACCESS 1クエリでの複数ALTER文

    ACCESS2007で複数テーブルのカラム属性を一括して変更したいのですが、1クエリで複数のALTER文は実行できないのでしょうか? 構文エラーで停止してしまいます。 ALTER TABLE TBL_ID1 ALTER COLUMN SEQ INTEGER; ALTER TABLE TBL_ID2 ALTER COLUMN SEQ INTEGER; どなたか御教え願えないでしょうか。

  • MYSQLチューニング初心者です。

    MYSQLチューニング初心者です。 MYSQLでEXPLAINでチューニングをしている際以下の壁にぶち当たって打開方法がないか模索しております。 テーブル構造はダミーですがたとえばa1,a2,a3というテーブル下記記載 SELECT * from a1 left join a2 on a1.id = a2.id left join a3 on a3.id = a1.mailbox_id \G a1.id: INT 主キー a1.mailbox_id: INT INDEX a1.title: varchar(50) a1.body: text a1.created_at: datetime a2.id: INT 主キー a2.mail_address: varchar(50) INDEX a3.id: INT 主キー a3.priority: INT a3.mail_box_name: varchar(50) 上記テーブル構造にて(EXPLAIN SELECT a1.id as id,a1.title,a1.body,a1.created_at,a2.mail_address,a3.mail_box_name FROM a1 left join a2 on a1.id = a2.id left join a3 on a3.id = a1.mailbox_id WHERE a2.mail_address = 'hogehoge@test.jp' order by created_at DESC;)を行うと、 Using temporary; Using filesort;が必ず出てきて対処方法に詰まっています。 order句のcreated_atをINDEXにすればよいのかな?と思いつけてみたのですが、a1.idのプライマリキーが優先され、結局同じ症状が出ます。 マルチインデックスでa1.id,created_atを付加し、FORCE INDEXをしたときはUsing temporary; Using filesortは消えますが、ExplainのrefがNULLになってしまいます。 方向性としてはtemporaryを使わない、indexでのsortを可能にする方法です。 まとまっていない文章で申し訳ございません。どうぞご教授のほどよろしくお願いします。 >>データ量としてはa1・a2は50万レコード a3は10件ほどです。 >>mysql Ver 14.14 Distrib 5.1.40, for redhat-linux-gnu (i686) using readline 5.1

  • Mysql サブクエリの使い方

    table_A ---+------ id | count ---+------ 1 | 5 ---+------ 2 | 6 ---+------ 3 | 7 ---+------ table_B ---+----- id | sub_count ---+----- 1 | 2 ---+------ 2 | 2 ---+------ 3 | 5 ---+------ 1 | 3 ---+------ 2 | 4 ---+------ このようなテーブルで table_Aのcountと、table_Bのidでまとめたsub_countの合計が 一致しないidだけを抽出するために、 SELECT table_A.id FROM (SELECT sum(table_B.sub_count) FROM table_B GROUP BY table_B.id) AS B, table_A, table_B WHERE table_A.id = table_B.id AND table_A.count != sum(table_B.sub_cout) と書いてみましたが、うまくいきません。 ERROR 1111 (HY000): Invalid use of group function 何が悪いのでしょうか? mysqlも投稿も初心者です。 よろしくお願いします。

    • ベストアンサー
    • MySQL
  • postgres table作成時にindex付与

    MySQLでは以下のようにテーブル作成時にインデックス付与ができます。 CREATE TABLE test ( a int(11) NOT NULL DEFAULT '0', b int(11) DEFAULT NULL, PRIMARY KEY (a), KEY test_idx (b), -- ★インデックス その1 KEY test_idx2 (a) -- ★インデックス その2 ); ポスグレではできませんか?

  • EXPLAINのUsing filesortについて

    explain select * from address where area =1 order by id desc limit 10,1\G 上記クエリーをexplainで確認すると Extra: Using where; Using filesort が表示されてしまいます。 1. Using filesortを消したいのですが、idを降順で表示するのに order by id desc 以外の方法はありますか?このような場合、Using filesortは仕方ないのでしょうか? 2. Using whereは効率の悪いクエリーの要素になるのでしょうか?

    • ベストアンサー
    • MySQL
  • SQLインジェクションの対策

    SQLインジェクションの対策 いつもお世話になっております。 SQLインジェクションの対策についてお伺いいたします。 もともと↓のようなSQL文だったものを "select user_id from table where user_id='{$user_id}'" 以下のように変更しました。 "select user_id from table where user_id='" . mysql_real_escape_string($user_id) . "'" 以下のように実行されていたSQL文は select user_idfrom table where user_id='10001' and 'a'='a' ↓のようにエスケープ処理して実行されるようになりました。(入力値は「10001' and 'a'='a」) select user_id from table where user_id='10001\' and \'a\'=\'a' ですが、phpMyAdminで実行してみるとどちらのSQL文も同じ結果が取得できてしまいます。 これでは対策になっていないと思ったので、質問させていただきました。 (magic_quotes_gpcはoffに設定しています。) なにか他の方法がいいのでしょうか。 ご教示よろしくお願いいたします。 <環境> PHP 5.1.6 MySQL 5.0.45

    • ベストアンサー
    • PHP
  • MySQLのselect文の設定に関して

    MySQLのselect文で、IDを例えば10001~10010までを検索する場合はどうすればいいのか教えて頂けないでしょうか? select * from table_A where ???

    • ベストアンサー
    • MySQL