重要なお知らせ

「教えて! goo」は2025年9月17日(水)をもちまして、サービスを終了いたします。詳細はこちら>

電子書籍の厳選無料作品が豊富!

お世話になります。
MySQLで2つのテーブルを結合し検索を行ったところ、私のPCで検索結果が得られるまで136秒もかかってしまい、非常に困っています。
これを1~2秒程度に納めたいのですが、どこを改善していいのかご教示いただけないでしょうか。

検索でやりたいことは、
検索キーと一致するNAMEを検索し、これに対応するFILE_IDに対応づけられているすべてのCLASSとNAMEの組み合わせを検索し、
得られたCLASSとNAMEの組み合わせの検索数をその組み合わせごとに集計し、
検索数の多い順に、CLASSとNAMEの組み合わせを出力する、
というものです。

例えば、結合テーブルが次のような場合に検索キーを与えたときは、次のような結果を得るものです。

FILE_ID GOODS_ID CLASS NAME
1 1 1 商品A
1 2 1 商品B
2 1 1 商品A
2 2 1 商品B
2 3 2 商品C
3 1 1 商品A
3 3 2 商品C

検索キー「商品A」の場合の検索結果:
CLASS NAME NUMBER
1 商品A 3
1 商品B 2
2 商品C 2

検索キー「商品B」の場合の検索結果:
CLASS NAME NUMBER
1 商品A 2
1 商品B 2
1 商品C 1

検索キー「商品C」の場合の検索結果:
CLASS NAME NUMBER
1 商品A 2
1 商品C 2
1 商品B 1


(1)RELATION_GOODSテーブルの構造
CREATE TABLE IF NOT EXISTS RELATION_GOODS(
FILE_ID INT NOT NULL,
GOODS_ID INT NOT NULL,
PRIMARY KEY(FILE_ID, GOODS_ID),
INDEX FILE_ID(FILE_ID) USING BTREE,
INDEX GOODS_ID(GOODS_ID) USING BTREE)

(2)GOODSのテーブルの構造
CREATE TABLE IF NOT EXISTS GOODS(
GOODS_ID INT NOT NULL,
CLASS INT NOT NULL,
NAME VARCHAR(1000) NOT NULL,
PRIMARY KEY(GOODS_ID),
INDEX CLASS_GOODS(CLASS, NAME(255)) USING BTREE,
INDEX GOODS(NAME(255)) USING BTREE,
INDEX CLASS(CLASS) USING BTREE)

(3)検索式
SELECT B.CLASS, B.NAME, COUNT(*) AS NUMBER
FROM RELATION_GOODS AS A
INNER JOIN GOODS AS B ON(A.GOODS_ID = B.GOODS_ID)
WHERE A.FILE_ID IN(
SELECT A.FILE_ID
FROM RELATION_GOODS AS A
INNER JOIN GOODS AS B ON(A.GOODS_ID = B.GOODS_ID)
WHERE B.NAME LIKE 'DVD'
)
GROUP BY B.CLASS, B.NAME
ORDER BY NUMBER DESC

(4)諸情報
テーブル名 レコード数 データ型 照合順序 サイズ オーバーヘッド
GOODS 612,750 InnoDB utf8_general_ci 69.6 MiB --
RELATION_GOODS 12,352,490 InnoDB utf8_general_ci 485 MiB --

バージョン: MySQL5.6.24
(3)の実行時間:Showing rows 0 - 24 (30181 total, Query took 136.2325 seconds.)

質問者からの補足コメント

  • うーん・・・

    上記の例とは別の内容ですが、INSERTの例を書きます。

    INSERT INTO GOODS(GOODS_ID, CLASS, NAME) VALUES
    (1, 9, '商品A'),
    (2, 16, '商品B'),
    (3, 25, '商品C'),
    (5, 41, '商品D'),
    (6, 18, '商品E'),
    (7, 25, '商品F'),
    (8, 41, '商品G'),
    (9, 44, '商品H'),
    (10, 10, '商品I'),
    (11, 35, '商品J'),
    (12, 9, '商品K')
    (13, 42, '商品L');

      補足日時:2016/01/20 14:04
  • うーん・・・

    INSERT INTO RELATION_GOODS(FILE_ID, GOODS_ID) VALUES
    (1, 1),
    (1, 2),
    (1, 4),
    (1, 9),
    (1, 10),
    (1, 11),
    (1, 12),
    (2, 2),
    (2, 3),
    (2, 4),
    (2, 5),
    (2, 7),
    (2, 11),
    (2, 12),
    (3, 3),
    (3, 7),
    (4, 6),
    (4, 9),
    (5, 2),
    (5, 6),
    (5, 8),
    (5, 9),
    (5, 11),
    (5, 12);

      補足日時:2016/01/20 14:04
  • yambejp様
    貴重なアドバイスをありがとうございます。
    その後、SQL構文の効率化を進めるとともにハードウェア周りを見直したところ、3秒まで縮まりました。
    目標まで今ひとつですが、とりあえずボトルネックという位置づけからは解放されました。
    ありがとうございます。

    No.6の回答に寄せられた補足コメントです。 補足日時:2016/01/25 10:26

A 回答 (7件)

あ、ごめんなさい1個処理がぬけてた



SELECT D.CLASS,D.NAME,COUNT(*) AS NUMBER
FROM RELATION_GOODS AS A
INNER JOIN GOODS AS B ON A.GOODS_ID = B.GOODS_ID
AND B.NAME ='商品B'
INNER JOIN RELATION_GOODS AS C ON A.FILE_ID = C.FILE_ID
INNER JOIN GOODS AS D ON C.GOODS_ID = D.GOODS_ID
GROUP BY CLASS,NAME
ORDER BY NUMBER DESC
この回答への補足あり
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
3回実行してみたところ、結果はそれぞれ次のとおりとなりました。

Showing rows 0 - 24 (30181 total, Query took 133.4681 seconds.)
Showing rows 0 - 24 (30181 total, Query took 125.5760 seconds.)
Showing rows 0 - 24 (30181 total, Query took 123.6280 seconds.)

お礼日時:2016/01/23 19:21

explainで検証してみてください


一部インデックスが利いていないですね
GOODSテーブルのCLASS_GOODSを捨てて、3カラムでインデックスを貼ってみてはどうでしょう?

ALTER TABLE GOODS DROP INDEX CLASS_GOODS;
ALTER TABLE GOODS ADD INDEX ID_CLASS_GOODS(GOODS_ID,CLASS,NAME);

それでもインデックスが微妙なので、いくつかパターンを試してみるとよいかもしれません。

試しに集計テーブルをつくってためしてみてください

CREATE TABLE SHUKEI (CLASS INT NOT NULL,NAME VARCHAR(1000) NOT NULL,B_NAME VARCHAR(1000) NOT NULL,INDEX BNAME(B_NAME));

2回目以降は
TRUNCATE SHUKEI;

//データ投入
INSERT INTO SHUKEI
SELECT D.CLASS,D.NAME,B.NAME AS B_NAME
FROM RELATION_GOODS AS A
INNER JOIN GOODS AS B ON A.GOODS_ID = B.GOODS_ID
INNER JOIN RELATION_GOODS AS C ON A.FILE_ID = C.FILE_ID
INNER JOIN GOODS AS D ON C.GOODS_ID = D.GOODS_ID;

//結果表示
SELECT CLASS,NAME,COUNT(*) AS NUMBER
FROM SHUKEI
WHERE B_NAME ='商品B'
GROUP BY CLASS,NAME
ORDER BY NUMBER DESC
    • good
    • 0

よくよく考えたら・・・こういうことじゃない?



SELECT C.GOODS_ID,B.NAME,COUNT(*) AS NUMBER
FROM RELATION_GOODS AS A
INNER JOIN GOODS AS B ON A.GOODS_ID = B.GOODS_ID
AND B.NAME ='商品C'
INNER JOIN RELATION_GOODS AS C ON A.FILE_ID = C.FILE_ID
GROUP BY GOODS_ID,NAME
ORDER BY NUMBER DESC
    • good
    • 0

ちなみに・・・


60万件と1200万件のリレーションだとさすがに重いかもしれませんね
特定の期間やIDの範囲で集計用テーブルをつくっておくようなテクニックが必要かもしれません。

検索条件をうまく使えばパーティショニングなども効果があるかもしれません。
    • good
    • 0
この回答へのお礼

yambejp様からいただいたSQLをヒントに次のように工夫してみたところ、劇的に改善しました。
目標まで後もう少しといったところです。

SELECT A.CLASS, A.NAME, B.NUMBER
FROM GOODS AS A
INNER JOIN (
SELECT GOODS_ID, COUNT(*) AS NUMBER FROM RELATION_GOODS
WHERE FILE_ID IN(
SELECT FILE_ID FROM RELATION_GOODS
WHERE GOODS_ID IN(
SELECT GOODS_ID FROM GOODS
WHERE NAME = '商品C'))
GROUP BY GOODS_ID
ORDER BY NUMBER DESC
) AS B ON(A.GOODS_ID = B.GOODS_ID)

Showing rows 0 - 24 (30181 total, Query took 9.0272 seconds.)
Showing rows 0 - 24 (30181 total, Query took 9.5128 seconds.)
Showing rows 0 - 24 (30181 total, Query took 9.5674 seconds.)

お礼日時:2016/01/20 21:14

とりあえずWHERE IN をはじいてみました


サンプルは同じ結果を返すと思うのですがちょっと自信なし
高速化しているかどうかは大規模なデータでテストしてみないとわかりません。

SELECT B.CLASS,B.NAME,COUNT(*) AS NUMBER
FROM RELATION_GOODS AS A
INNER JOIN
(SELECT FILE_ID
FROM RELATION_GOODS AS A2
INNER JOIN GOODS AS B ON A2.GOODS_ID = B.GOODS_ID
WHERE B.NAME ='商品C'
) AS A2 USING(FILE_ID)
INNER JOIN GOODS AS B ON A.GOODS_ID = B.GOODS_ID
GROUP BY B.CLASS, B.NAME
ORDER BY NUMBER DESC
    • good
    • 1
この回答へのお礼

ご回答ありがとうございます。
3回実行してみたところ、結果はそれぞれ次のとおりとなりました。

Showing rows 0 - 24 (30181 total, Query took 130.8610 seconds.)
Showing rows 0 - 24 (30181 total, Query took 116.9526 seconds.)
Showing rows 0 - 24 (30181 total, Query took 121.1926 seconds.)

お礼日時:2016/01/20 20:12

例示したデータと、テーブルの構造があっていないので


いまいち何がしたいのかわかりません。

ちなみにFILE_IDとCLASSの用途はなんなのでしょう?

検索式についてはWHERE句でINを利用しているところが
ちょっと気になるかな・・・
またB.NAMEにワイルドカードなしでLIKEしていますが
完全一致なら「=」の方が効率がよいかも
またVARCHAR(1000)とかちょっと検索性を確保するにはどうかなぁ・・・

いずれにしろテストできていないので、
RELATION_GOODSテーブルとGOODSテーブルに
INSERT INTOで仮データを投入する例も書いてもらった方がよいかと
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。

> 例示したデータと、テーブルの構造があっていないので
> いまいち何がしたいのかわかりません。

例示したデータは、テーブルを結合した後のものなので、元のテーブルの構造で示すと次のとおりです。


(1)RELATION_GOODSテーブル

FILE_ID GOODS_ID
1 1
1 2
2 1
2 2
2 3
3 1
3 3

(2)GOODSのテーブル

GOODS_ID CLASS NAME
1 1 商品A
2 1 商品B
1 1 商品A
2 1 商品B
3 2 商品C
1 1 商品A
3 2 商品C


> ちなみにFILE_IDとCLASSの用途はなんなのでしょう?

1つのファイルに対して一意のFILE_IDが設定され、
1つのファイルで複数の商品を管理しているという構造になっています。
上記の例では、
ファイル1(FILE_ID=1)では商品A、Bの2つを管理し、
ファイル2では商品A~Cの3つを管理し、
ファイル3では商品A、Cの2つを管理している、
という構造です。


> またB.NAMEにワイルドカードなしでLIKEしていますが
?完全一致なら「=」の方が効率がよいかも

完全一致ですので、「=」に改めたいと思います。


> またVARCHAR(1000)とかちょっと検索性を確保するにはどうかなぁ・・・

長い商品名があり、これに対応するために、1000に設定されています。

INSERTの例は、文字数の関係がありますので、補足欄でお伝えします。

お礼日時:2016/01/20 14:02

まずは遅い原因がCPUなのかストレージなのかなど原因を突き止めるのが先決でしょう。


その上でCPUが遅ければ速いマシンを使うとか、ストレージをSSDにするとかオンメモリでDBを使うなどの対応をする。

うちは原則として必要以上に人手(プログラムやクエリの改修、チューニング)をかけません。
金で解決できること(CPU交換やSSD搭載)は金で解決するほうが結果的に安上がりなことがほとんどなので。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
ハードウェアまわりも確認してみたいと思います。

ちなみに、現在の開発環境は、CPU 2GHz、RAM 4G、HDD、Windows7です。
しかし、あくまで試作なので、大きな予算がとれず、ハードウェアまわりを増強するのは優先順位が高くないというのが予算上の現状です。

お礼日時:2016/01/20 12:22

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

関連するカテゴリからQ&Aを探す