質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

MariaDB

MariaDBは、MySQL派生のオープンソースなリレーショナルデータベースシステムです。 また、MySQLとほぼ同じデータベースエンジンに対応しています。

Q&A

解決済

6回答

8186閲覧

集計関数を利用した場合のORDER BY高速化

aglkjggg

総合スコア769

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

MariaDB

MariaDBは、MySQL派生のオープンソースなリレーショナルデータベースシステムです。 また、MySQLとほぼ同じデータベースエンジンに対応しています。

0グッド

0クリップ

投稿2016/05/16 21:32

編集2016/05/17 06:00

###前提・実現したいこと
ORDER BYをつけると激遅になります。
これを解決する方法はありませんか?

今のテーブルは500万行ですが、今後も増え続けます。

SQL

1SELECT 2 accounts.id, 3 COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1, 4 COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2 5FROM 6 accounts 7GROUP BY 8 accounts.id 9LIMIT 10000; 10-- Duration for 1 query: 0.063 sec. (+ 1.078 sec. network)

SQL

1SELECT 2 accounts.id, 3 COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1, 4 COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2 5FROM 6 accounts 7GROUP BY 8 accounts.id 9ORDER BY 10 count1, 11 count2 12LIMIT 10000; 13-- Duration for 1 query: 40.610 sec. (+ 0.078 sec. network)

###補足情報(言語/FW/ツール等のバージョンなど)

  • MariaDBのバージョン

Ver 15.1 Distrib 10.1.11-MariaDB, for Linux (x86_64) using readline 5.1

  • テーブルのインデックス

accounts.idaccounts.itemにINDEXはついています。
それぞれ単独でのキーをつけています。

  • 実行計画(EXPLAINの結果)

イメージ説明

  • 複合キーを付けた場合(2016/05/17 14:02追記)

id, itemの順に複合キーをつけた場合早くなりました。
Duration for 1 query: 6.688 sec. (+ 1.406 sec. network)
しかし、1秒台ぐらいには抑えたいと思っております。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KiyoshiMotoki

2016/05/17 02:48

インデックスは、"id"と"account"それぞれのカラムに単独のインデックスがついていますか?それとも複合インデックスですか?複合インデックスの場合、カラムを指定する順番はどうなっていますか?
退会済みユーザー

退会済みユーザー

2016/05/17 04:36

explain select 〜 の結果をご提示ください。
aglkjggg

2016/05/17 04:43

お二方ご意見ありがとうございます。情報を追加しました。
退会済みユーザー

退会済みユーザー

2016/05/17 05:28

Explain の結果、見ることができませんよ。
aglkjggg

2016/05/17 06:00

申し訳ありません。画像に変更しました。
guest

回答6

0

ベストアンサー

DB 設計にそれほど詳しくないので、他のよい回答がなされることを期待しておりますが、高速化するための一つの方策は正規化レベルを落とすことではないかと思います。

今回の SQL を発行する場合、全レコードをなめないと COUNT() の値が出ませんし、それに基づいたソートもできないことになりますので、件数が増えると (主にソート処理で) さらに激重になると予想されます。

もし、データ登録時の時間が多少増えてもよいのであれば、count1count2 相当は別テーブルに集計し (たとえば、count テーブルに key カラムと value カラムがある、とか)、SELECT 時は JOIN するとよいのではないでしょうか。
データ登録時にかかる時間との兼ね合いになりますが、おそらく count1, count2 相当のレコードにはインデックスを張り、場合によっては accounts テーブルの INSERT にトリガを設定して count テーブルを自動更新するとかも (MariaDB にトリガがあるのかは知りませんけれども)。

投稿2016/05/17 00:00

unau

総合スコア2468

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

MySQLの設定ファイル(my.cnf/my.ini)にsort_buffer_sizeという設定がありますので、
メモリをガンガンつかってもよいのであれば、これを拡張しても高速化が見込めるかと。

投稿2016/05/17 04:43

koufukurairai

総合スコア64

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aglkjggg

2016/05/17 05:26

query_cache_type = 1 query_cache_size = 512M query_cache_limit = 128M このように設定した所2回目以降のクエリは爆速になりました。 query: 0.031 sec. (+ 1.000 sec. network) しかしメモリを使う為SQLの書き方で改善できない場合のみ利用したいと思います。
koufukurairai

2016/05/17 05:35 編集

キャッシュのメモリを拡張することと、バッファを拡張することは別ですが、バッファじゃ早くなりませんでしたか?
aglkjggg

2016/05/17 05:53 編集

申し訳ありません>< 大いにミスリードして、 勝手に「sort_buffer_size」が「query_cache_...」に脳内変換されてました・・・。 結論からすると早くなりませんでした。 query_cache_type=0 #query_cache_size = 512M #query_cache_limit = 128M にした後、 sort_buffer_size = 512M としました。 Duration for 1 query: 42.010 sec. (+ 0.080 sec. network)
guest

0

Limit10000としていますが、上位10000には明らかに届かない値がわかっているなら、それをあらかじめ省いてソート対象行を減らすなどどうでしょう?

例えばcount1の平均値以上は上位10000件には絶対に含まれない場合、次のようなイメージです。
※実際に環境を準備できないので、早くなるのかわかりません...
(また動作テストもしていません。AVGの中でサブクエリ動作しましたっけ?)

SQL

1SELECT 2 accounts.id, 3 COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1, 4 COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2 5FROM 6 accounts 7GROUP BY 8 accounts.id 9HAVING COUNT(CASE item WHEN 1 THEN 1 END) < AVG( 10 SELECT COUNT(item) 11 FROM accounts 12 WHERE item = 1 13) 14ORDER BY 15 count1, 16 count2 17LIMIT 10000;

投稿2016/05/17 03:51

編集2016/05/17 04:06
hirohiro

総合スコア2068

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hirohiro

2016/05/17 04:05

HAVINGの仕様を良く知らないまま書いてしまいましたが、ひょっとすると実行前に対象列のソートを行うかも知れません。もしそうならSQLが遅くなるだけだったかも... ( SELECT id,count1 FROM( SELECT id, COUNT(item) as count1 FROM accounts WHERE item = 1 GROUP BY id ) d1 WHERE count1 > 1000 ) サブクエリ階層が深くなって複雑化しますが、こういうのに、count2の集計結果をjoinしてソートするほうがいいかも知れません。 ※これはcount1が1000以上の行は明らかに上位10000に届かないことが解っている場合。
aglkjggg

2016/05/17 06:58

ご回答有り難うございます。 しかし、値はcount1, count2のどちらも1,2,3,4のどれかとなり、 一定値以下(以上)での判定はでなかなか難しいです。 (1の値が多いという、値の偏りがかなり強いです。)
hirohiro

2016/05/17 07:24 編集

なるほど。ちょっとSQLや質問の本質とは離れますが、 >>count1, count2のどちらも1,2,3,4のどれか ということは、16通りの組み合わせしか無いことになります。 それをソートして上位10000件を取り出すとしても、10000番と10001番に差は無いのでは無いでしょうか? むしろ10000番付近は同じ値がずっと並んでいると思います。 そうすると、それより上位の値は確実に抽出候補になり、境界を含む値はどれが選ばれても良いわけなので、ソートする必要がない。ということに成りませんかね? 1. count1=1,count2=1の行を抽出して、行数をカウント 2-1. 10000未満ならはcount1=1,count2=2の行を抽出して、行数をカウント 3-1. 合計行数が10000未満なら... 3-2. 10000以上なら2-1から丁度合計が10000になるだけ適当に抽出して1-1と連結 こんな事が出きれば良いわけですが、プロシージャで作ってみるのも手かも知れません。
guest

0

CREATE TEMPORARY TABLES権限が必要となりますが、インデックス付きの一時テーブルを利用してみてはいかがでしょうか?
https://mariadb.com/kb/en/mariadb/create-table/

例えば、以下のような感じです。

sql

1CREATE TEMPORARY TABLE tmp ( 2 id INT NOT NULL, 3 count1 INT NOT NULL, 4 count2 INT NOT NULL, 5 6 INDEX (count1, count2) 7); 8 9INSERT INTO tmp SELECT 10 accounts.id, 11 COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1, 12 COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2 13FROM 14 accounts 15GROUP BY 16 accounts.id; 17 18SELECT id, count1, count2 FROM tmp ORDER BY count1, count2 LIMIT 10000;

投稿2016/05/17 04:40

KiyoshiMotoki

総合スコア4791

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aglkjggg

2016/05/17 04:56

ご回答有り難うございます。 しかし、2番目のクエリ(INSERT INTO)に時間がとてもかかりました。 accountsテーブルにある行数分(今は500万行)を一時表にINSERTする為、 相当な時間がかかってしまっているようです。 最終的に、3番目のクエリ(SELECT)の結果取得までに1分以上かかってしまいました。 Duration for 3 queries: 00:01:18 (+ 0.063 sec. network)
guest

0

ソート対象のレコード数が多いですね。

以下のSQLは全く同じ環境・テーブルで試したわけではないのでダメだったらスルーして下さい。
(こちらでは130万レコード程度で試しました)

SQL

1SELECT 2 tmp_accounts.id, 3 tmp_accounts.count1, 4 tmp_accounts.count2 5 FROM (SELECT accounts.id, 6 COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1, 7 COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2 8 FROM 9 accounts 10 GROUP BY 11 accounts.id 12 LIMIT 10000) as tmp_accounts 13 ORDER BY 14 tmp_accounts.count1, 15 tmp_accounts.count2 ;

投稿2016/05/17 04:18

tonarino210

総合スコア228

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tonarino210

2016/05/17 04:31

スルーして下さい。豪快に勘違いしておりました・・・orz
guest

0

私はMySqlでそこまでの件数を扱ったことがないのでやったことがないのですが、Order byにもインデックスが使われるのではなかったでしょうか。

count1とcount2の複合インデックスを試してみられてはどうでしょう。

投稿2016/05/16 22:05

munyagu

総合スコア479

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aglkjggg

2016/05/16 22:14 編集

ご回答有り難うございます。 しかし、count1とcount2はCOUNT関数を利用し、accounts.itemを数えたものです。 INDEXを付けたいとは思いましたが、COUNT関数を利用した結果ですので不可能でした。
munyagu

2016/05/16 23:10

ああ、申し訳ありません、そこまで見れていませんでした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問