1

すべての MySQL エキスパート向けの 1 つ :-)

次のクエリがあります。

SELECT o.*, p.name, p.amount, p.quantity 
FROM orders o, products p 
WHERE o.id = p.order_id AND o.total != '0.00' AND DATE(o.timestamp) BETWEEN '2012-01-01' AND '2012-01-31' 
ORDER BY o.timestamp ASC
  • 注文テーブル = 80,900 行
  • 製品テーブル = 125,389 行
  • o.id と p.order_id はインデックス化されています

クエリが完了するまでに約 6 秒かかります。これは長すぎます。おそらく一時テーブルまたは別のタイプの結合を使用して、最適化する方法を探しています。残念ながら、これらの概念の両方に対する私の理解はかなり限られています。

このクエリを最適化する方法を誰かが提案できますか?

4

4 に答える 4

2

私はMySQLの専門家ではありません(より多くのSQL Server)。o.timestampにインデックスを付けた方がいいと思います。クエリを次のように書き直す必要があります。

o.timestamp >= '2012-01-01' and o.timestamp <= '2012-01-31' + INTERVAL 1 DAY

論理は次のとおりです。列と定数の式を比較すると、インデックスは機能しません。列と定数を比較する必要があります

于 2012-10-16T10:43:56.440 に答える
2
  1. Explainを使用して、クエリを最適化する方法を示します。Total と TimeStamp のインデックスから始めることをお勧めします

  2. 関数を削除すると、dateパフォーマンスが向上する場合があります。

  3. 最新の構文を使用する必要があります。

例えば。

SELECT o.*, p.name, p.amount, p.quantity  
FROM orders o
     inner join products p  
     on o.id = p.order_id 
WHERE o.total != '0.00' 
AND o.timestamp BETWEEN '2012-01-01' AND '2012-01-31 23:59'  
ORDER BY o.timestamp ASC 
于 2012-10-16T10:51:21.317 に答える
2

まず、別のスタイルの構文を使用します。 ANSI-92就寝するのに20年かかりましたが、多くのRDBMSは実際にあなたが使用した表記法を使用しないことを推奨しています. この場合、違いはありませんが、多くの理由から、実際には非常に良い習慣です(調査して自分で判断してもらいます)

最終的な回答と構文例:

SELECT
  o.*, p.name, p.amount, p.quantity  
FROM
  orders
INNER JOIN
  products
    ON orders.id = products.order_id 
WHERE
      orders.timestamp >= '2012-01-01'
  AND orders.timestamp <  '2012-02-01'
  AND orders.total     != '0.00' 
ORDER BY
  orders.timestamp ASC

ordersテーブルは最初のフィルタリングを行っているテーブルであるため、最適化を検討するのに非常に適した場所です。


1 月のすべての日付と時刻を取得することDATE(o.timestamp) BETWEEN x AND yに成功しました。ただし、それには、テーブル内のすべての行DATE()で関数を呼び出す必要があります(RBAR の意味と同様)。RDBMS は、時間の浪費を避ける方法を知るためだけに関数を見通すことはできません。代わりに、フィルタリングしているフィールドで関数を必要としないように数学を再配置することにより、その最適化を行う必要があります。orders

    orders.timestamp >= '2012-01-01'
AND orders.timestamp <  '2012-02-01'

このバージョンでは、オプティマイザーは、互いに連続した日付のブロックが必要であることを認識できます。いわゆるレンジシークです。インデックスを使用して、その範囲に適合する最初のレコードと最後のレコードを非常に迅速に見つけ、その間にあるすべてのレコードを選択できます。これにより、適合しないすべてのレコードのチェックが回避され、さらに範囲の中間にあるすべてのレコードのチェックが回避されます。境界だけを探す必要があります。

これは、すべてのレコードが日付順に並べられており、オプティマイザーがそれを確認できることを前提としています。そのためには索引が必要です。それを念頭に置いて、使用できる2つの基本的なカバーインデックスがあるようです:
- (id, timestamp)
-(timestamp, id)

1 つ目は、人々が最もよく使用しているものです。しかし、これにより、オプティマイザーはそれぞれに対して個別にtimestampレンジシークを行う必要があります。idそして、すべてidの可能性が異なるtimestamp値を持っているため、何も得られません.

2番目のインデックスは、私が推奨するものです。

これで、オプティマイザーはクエリのこの部分を非常に迅速に満たすことができます...

SELECT
  o.*
FROM
  orders
WHERE
      orders.timestamp >= '2012-01-01'
  AND orders.timestamp <  '2012-02-01'
ORDER BY
  orders.timestamp ASC

たまたま、ORDER BY提案されたインデックスで最適化されています。データを出力したい順序になっています。結合後にすべてを再ソートする必要はありません。


次に、total != '0.00'要件を満たすために、範囲内のすべての行が引き続きチェックされます。しかし、すでに範囲をかなり狭めているので、おそらくこれで問題ありません。 (ここでは説明しませんが、MySQL でインデックスを使用してこれ範囲シークを最適化することは不可能であることがわかるでしょう。)timestamp

次に、参加します。これは、既に持っているインデックスによって最適化されています(products.order_id)。上記のスニペットによって選択されたすべてのレコードに対して、オプティマイザーはインデックス シークを実行し、一致するレコードを非常に迅速に特定できます。


これはすべて、ほとんどの場合、すべての注文行に 1 つ以上の製品行があることを前提としています。たとえば、ごく一部の注文のみが製品行を持っていた場合、関心のある製品行を最初に選択する方が速い場合があります。基本的に、結合が逆の順序で発生していることを確認します。

オプティマイザーは実際にその決定を行いますが、オプティマイザーがそれを行っていることを知っておくと便利です。次に、最も役立つと推定されるインデックスを提供します。

説明計画をチェックして、インデックスが使用されているかどうかを確認できます。そうでない場合、あなたの支援の試みは無視されました。おそらく、別の順序で結合する方が良いことを示唆するデータの統計のためです。その場合は、代わりにインデックスを提供して、結合の順序を助けることができます。

于 2012-10-16T11:22:28.133 に答える
1

選択する *:

* ワイルドカードを使用してすべての列を選択すると、テーブルのスキーマが変更された場合にクエリの意味と動作が変化し、クエリで大量のデータが取得される可能性があります。

!= 演算子は非標準です:

代わりに <> 演算子を使用して不等式をテストしてください。

AS キーワードを使用しないエイリアス: 「tbl AS エイリアス」などの列またはテーブルのエイリアスで AS キーワードを明示的に使用すると、「tbl エイリアス」などの暗黙的なエイリアスよりも読みやすくなります。

于 2012-10-16T10:49:44.353 に答える