web-dev-qa-db-ja.com

5000万のテーブルに参加するのに3分はかかりすぎですか?

これが間違った場所である場合はお詫びしますが、私のクエリが適切かどうかについては本当に意見が必要です。

基本的に、グループごとの古いエントリと無効なエントリの数を取得する必要があります。これは、NULL値または単に1か月前の値で識別されます。選択したデータは、「統計」と呼ばれる小さなテーブルに保存されます。 PHPはcronスクリプトに使用されます。

さて、問題は私がまだ初心者なので、クエリが高速か低速かわからないことです。次のクエリを完了するには、約3分かかります。上司によると遅いため、少なくとも50%高速にする必要がありますが、スピードアップのために何かできるかどうか私自身はわかりません。 「cat_01」テーブルには50ミルの行があり、他のテーブルには2000未満です。

使用したすべての列にインデックスを追加したため、時間が6分から3分に短縮されました。

私が使用しているクエリは次のとおりです(テーブルと列に仮名を使用したので、ロジックは気にしないでください)。

SELECT 
'cat_01' as category,
m.shop_id,
o.name,
count(m.shop_id) as total,
tr2.traffic,
IF(mm.bought IS NULL,0,mm.bought) as bought 
FROM cat_01 m 
JOIN shops o 
ON m.shop_id = o.id 
JOIN (
    select 
    mmm.shop_id,
    max(mmm.buy_date) as bought 
    FROM cat_01 mmm 
    GROUP BY mmm.shop_id
) mm
ON o.id = mm.shop_id 
LEFT JOIN (
    SELECT tr.shop_id, sum(tr.sales) AS traffic 
    FROM (
        SELECT
        mmmmm.shop_id,
        bs.sales,
        bs.order_id,
        bs.id
        FROM cat_01 mmmmm
        JOIN orders bs
        ON mmmmm.order = bs.order_id 
    ) tr group by tr.shop_id
) tr2
ON tr2.shop_id = mm.shop_id 
WHERE (m.buy_date IS NULL)
OR (m.buy_date <  UNIX_TIMESTAMP())
GROUP BY m.shop_id
  • テーブル「cat01」を作成します。
CREATE TABLE `cat_01` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `shop_id` int(11) DEFAULT '0',
 `buy_date` int(11) DEFAULT NULL,
 `order` int(11) DEFAULT '0',
 PRIMARY KEY (`id`),
 KEY `shop_id` (`shop_id`),
 KEY `buy_date` (`buy_date`),
 KEY `order` (`order`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
  • テーブル「ショップ」を作成します。
CREATE TABLE `shops` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
  • テーブル「注文」を作成します。
CREATE TABLE `orders` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `order_id` int(11) NOT NULL,
 `sales` int(11) NOT NULL DEFAULT '0',
 `status` tinyint(1) NOT NULL DEFAULT '1',
 PRIMARY KEY (`id`),
 KEY `order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


これが説明です(自宅からライブデータにアクセスできないため、ダミー行を使用しましたが、残りは同じです)

explain

[〜#〜] edit [〜#〜]:例としてUNIX_TIMESTAMP()を追加

EDIT2:SELECTからサブクエリを削除して別々に使用し、PHPループを使用して、結果を混合して適切なINSERTクエリを作成しました。

追伸sctackechangeでのSQLコードのフォーマットがどのように見えるかの直接的な例を見つけることができないようです。そのため、文字化けが発生した場合は、すみやかに修正してみます。

私はEXPLAIN結果を持っていますが、それをフォーマットする方法がわかりません、後で画像を投稿する可能性があります。

特定の情報が必要な場合は、遠慮なく尋ねてください。私は何を調べればよいかわかりません。

2
OtakuSparrow
FROM ( SELECT ... ) JOIN ( SELECT ... )

うまく最適化されません。クエリを作成するより良い方法を考えてください。それが失敗した場合は、サブクエリの1つをTEMPORARY TABLEに入れ、それにインデックスを追加します。

日付には、DATEではなくINTのデータ型の使用を検討してください。

ORはパフォーマンスを向上させます(インデックスを使用できないためです)。 IS NULLを処理する他の方法を検討してください。

その他

order_idordersで一意である場合、idとユーザーorder_idPRIMARY KEYとして削除します。 (さらに他のテーブルに必要な変更はありますか?)PKによる検索は、2次キーによる検索よりも高速です。

これらのテーブルの一部の情報が「静的」である(一度書き込まれたが、更新または削除されていない)場合は、「要約テーブル」の作成と保守を検討してください。

このクエリを13のカテゴリに対して13回実行する場合、GROUP BY category_idを使用すると、1回の実行がmay速くなります。

ORUNIONmayに変換すると、特により良いインデックスを使用できる場合にスピードアップします。

1
Rick James

まず、次のインデックスを追加します。

ALTER TABLE cat_01
ADD INDEX `ShopBuyDates` (`shop_id`,`buy_date`)

ALTER TABLE orders
ADD INDEX `OrderSales` (`order_id`,`sales`)

次に、このクエリを試して、結果を報告します。少なくとも2回は実行し、最初の結果のパフォーマンスを破棄して、キャッシュへのデータ入力の影響を洗い流してください。

SELECT 
'cat_01' as category,
m.shop_id,
o.name,
count(m.shop_id) as total,
tr2.traffic,
IF(mm.bought IS NULL,0,mm.bought) as bought 
FROM cat_01 m 
JOIN shops o 
ON m.shop_id = o.id 
JOIN (
    select 
    mmm.shop_id,
    max(mmm.buy_date) as bought 
    FROM cat_01 mmm 
    GROUP BY mmm.shop_id
) mm
ON o.id = mm.shop_id 
LEFT JOIN (
    SELECT mmmmm.shop_id, sum(bs.sales) AS traffic 
    FROM cat_01 mmmmm
    JOIN orders bs
    ON mmmmm.order = bs.order_id 
    group by mmmmm.shop_id
) tr2
ON tr2.shop_id = mm.shop_id 
WHERE (m.buy_date IS NULL)
OR (m.buy_date = 0)
OR (m.buy_date < 1)
GROUP BY m.shop_id

これによる改善を確認した後(このクエリからの新しいEXPLAINも良いでしょう)、反復してクエリの他の原因を確認できます。

1
Willem Renzema