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

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

ただいまの
回答率

90.47%

  • MySQL

    6002questions

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

  • SQL

    2470questions

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

  • MariaDB

    305questions

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

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

解決済

回答 6

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,425

aglkjggg

score 709

前提・実現したいこと

ORDER BYをつけると激遅になります。
これを解決する方法はありませんか?

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

SELECT
    accounts.id,
    COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1,
    COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2
FROM
    accounts
GROUP BY
    accounts.id
LIMIT 10000;
-- Duration for 1 query: 0.063 sec. (+ 1.078 sec. network)
SELECT
    accounts.id,
    COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1,
    COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2
FROM
    accounts
GROUP BY
    accounts.id
ORDER BY
    count1,
    count2
LIMIT 10000;
-- 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秒台ぐらいには抑えたいと思っております。
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • aglkjggg

    2016/05/17 13:43

    お二方ご意見ありがとうございます。情報を追加しました。

    キャンセル

  • Kosuke_Shibuya

    2016/05/17 14:28

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

    キャンセル

  • aglkjggg

    2016/05/17 15:00

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

    キャンセル

回答 6

checkベストアンサー

+3

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

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

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

SELECT
    accounts.id,
    COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1,
    COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2
FROM
    accounts
GROUP BY
    accounts.id
HAVING COUNT(CASE item WHEN 1 THEN 1 END) < AVG(
    SELECT COUNT(item)
    FROM accounts
    WHERE item = 1
)
ORDER BY
    count1,
    count2
LIMIT 10000;

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/17 13: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に届かないことが解っている場合。

    キャンセル

  • 2016/05/17 15:58

    ご回答有り難うございます。

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

    キャンセル

  • 2016/05/17 16:22 編集

    なるほど。ちょっと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と連結
    こんな事が出きれば良いわけですが、プロシージャで作ってみるのも手かも知れません。

    キャンセル

+1

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/17 14:26

    query_cache_type = 1
    query_cache_size = 512M
    query_cache_limit = 128M

    このように設定した所2回目以降のクエリは爆速になりました。
    query: 0.031 sec. (+ 1.000 sec. network)

    しかしメモリを使う為SQLの書き方で改善できない場合のみ利用したいと思います。

    キャンセル

  • 2016/05/17 14:35 編集

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

    キャンセル

  • 2016/05/17 14:52 編集

    申し訳ありません><
    大いにミスリードして、
    勝手に「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)

    キャンセル

0

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/17 07:13 編集

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

    キャンセル

  • 2016/05/17 08:10

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

    キャンセル

0

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

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

SELECT
    tmp_accounts.id,
    tmp_accounts.count1,
    tmp_accounts.count2
  FROM (SELECT accounts.id,
               COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1,
               COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2
          FROM
            accounts
          GROUP BY
            accounts.id
          LIMIT 10000) as tmp_accounts
  ORDER BY
    tmp_accounts.count1,
    tmp_accounts.count2 ;

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/17 13:31

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

    キャンセル

0

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

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

CREATE TEMPORARY TABLE tmp (
    id INT NOT NULL,
    count1 INT NOT NULL,
    count2 INT NOT NULL,

    INDEX (count1, count2)
);

INSERT INTO tmp SELECT
    accounts.id,
    COUNT(CASE accounts.item WHEN 1 THEN 1 END) as count1,
    COUNT(CASE accounts.item WHEN 2 THEN 1 END) as count2
FROM
    accounts
GROUP BY
    accounts.id;

SELECT id, count1, count2 FROM tmp ORDER BY count1, count2 LIMIT 10000;

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/17 13:56

    ご回答有り難うございます。
    しかし、2番目のクエリ(INSERT INTO)に時間がとてもかかりました。
    accountsテーブルにある行数分(今は500万行)を一時表にINSERTする為、
    相当な時間がかかってしまっているようです。

    最終的に、3番目のクエリ(SELECT)の結果取得までに1分以上かかってしまいました。
    Duration for 3 queries: 00:01:18 (+ 0.063 sec. network)

    キャンセル

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

  • ただいまの回答率 90.47%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    MySQLを使って年齢で世代ごとの人数を表示するプログラム

    まだ勉強し始めて三日ほどしか経っていないMySQL初心者です。 現在の日付と生年月日から年齢を求める式を用いて、世代ごとの人数を表示するプログラムを書いています。 case文を

  • 受付中

    ORDER BY RAND(); が遅い

     質問 タイトルの通りなのですが、 MariaDBにおいてORDER BY RAND()が非常に遅いため改善を試みましたが、 上手くいかなかった為、 「どうすれば結果取得ま

  • 解決済

    MySQLのテーブル作成について

    CREATE TABLE T_VendorShohin( F_ShohinName nvarchar(30) not null primary key, F_ShohinPrice

  • 解決済

    ユーザ定義変数に配列を代入したい

    前提・実現したいこと タイトルの通りですが、 ユーザー定義変数に配列(ベクトル)を代入したいのですが上手く出来ませんでした。 そもそも、スカラー値しか代入できないのでしょうか?

  • 解決済

    UPDATEを高速にしたい

    前提・実現したいこと UPDATEが非常に遅いので速くしたいです。 accountsテーブルに約1千万件のデータが入っています。 そのうち約180万件に対してUPDATEをする必要

  • 解決済

    updateとcase whenの相性について

    +------+------+------+----------+ | bang | uria | tuki | bikou    | +------+------+------+

  • 解決済

    SQLにおける日時検索

    毎日毎時間10分単位で時間と風向・風量を測定しDBに記録をしていく様な、添付した画像のテーブルがあります。 そこで質問ですが、このテーブルから 「30分毎のレコード」や「

  • 解決済

    CONCATでCOUNTをすると正しく表示されない

     前提・実現したいこと 表1の出力を得たいです。 表1. 理想の出力 col1 col2 col3 1 1a 1a  発生している問題・エラーメッセージ

同じタグがついた質問を見る

  • MySQL

    6002questions

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

  • SQL

    2470questions

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

  • MariaDB

    305questions

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