1

このクエリをこのように実行すると、user_fans テーブルに10000人のユーザー エントリが含まれているため、実行に時間がかかります。どうすれば最適化できますか?

クエリ

SELECT uf.`user_name`,uf.`user_id`,
@post                := (SELECT COUNT(*) FROM post WHERE user_id = uf.`user_id`) AS post,
@post_comment_likes  := (SELECT COUNT(*) FROM post_comment_likes WHERE user_id = uf.`user_id`) AS post_comment_likes,
@post_comments       := (SELECT COUNT(*) FROM post_comments WHERE user_id = uf.`user_id`) AS post_comments,
@post_likes          := (SELECT COUNT(*) FROM post_likes WHERE user_id = uf.`user_id`) AS post_likes,

(@post+@post_comments) AS `sum_post`,
(@post_likes+@post_comment_likes) AS `sum_like`, 
((@post+@post_comments)*10) AS `post_cal`,      
((@post_likes+@post_comment_likes)*5) AS `like_cal`,
((@post*10)+(@post_comments*10)+(@post_likes*5)+(@post_comment_likes*5)) AS `total`  
FROM  `user_fans` uf  ORDER BY `total` DESC lIMIT 20
4

4 に答える 4

7

他のテーブルにトリガーを配置し、 User_Fans テーブルにいくつかの列を追加するだけで、これを完全に簡素化しようとします...取得しようとしているそれぞれの count() ごとに1つ... Posts、PostLikes、PostComments、投稿コメントいいね。

どちらのテーブルにレコードが追加された場合でも、user_fans テーブルを更新してカウントに 1 を追加するだけです。とにかく、ユーザーのキー ID に基づいて事実上瞬時に行われます。「LIKES」については... 同様に、何かが「Like」としてトリガーされるという条件の下でのみ、1 を追加します「加重」合計値。テーブルがさらに大きくなると、流し込んで集計するデータが増えるため、クエリも長くなります。本質的に他のすべてのテーブルからすべてのレコードを照会しているすべての user_fan レコードを通過しています。

そうは言っても、テーブルをそのままにして、次のように再構築します...

SELECT 
      uf.user_name,
      uf.user_id,
      @pc := coalesce( PostSummary.PostCount, 000000 ) as PostCount,
      @pl := coalesce( PostLikes.LikesCount, 000000 ) as PostLikes,
      @cc := coalesce( CommentSummary.CommentsCount, 000000 ) as PostComments,
      @cl := coalesce( CommentLikes.LikesCount, 000000 ) as CommentLikes,
      @pc + @cc AS sum_post,
      @pl + @cl AS sum_like, 
      @pCalc := (@pc + @cc) * 10 AS post_cal,
      @lCalc := (@pl + @cl) * 5 AS like_cal,
      @pCalc + @lCalc AS `total`
   FROM
      ( select @pc := 0,
               @pl := 0,
               @cc := 0,
               @cl := 0,
               @pCalc := 0
               @lCalc := 0 ) sqlvars,
      user_fans uf
        LEFT JOIN ( select user_id, COUNT(*) as PostCount
                       from post
                       group by user_id ) as PostSummary
           ON uf.user_id = PostSummary.User_ID

        LEFT JOIN ( select user_id, COUNT(*) as LikesCount
                       from post_likes
                       group by user_id ) as PostLikes
           ON uf.user_id = PostLikes.User_ID

        LEFT JOIN ( select user_id, COUNT(*) as CommentsCount
                       from post_comment
                       group by user_id ) as CommentSummary
           ON uf.user_id = CommentSummary.User_ID

        LEFT JOIN ( select user_id, COUNT(*) as LikesCount
                       from post_comment_likes
                       group by user_id ) as CommentLikes
           ON uf.user_id = CommentLikes.User_ID

   ORDER BY 
      `total` DESC 
   LIMIT 20

My variables are abbreviated as 
"@pc" = PostCount
"@pl" = PostLikes
"@cc" = CommentCount
"@cl" = CommentLike
"@pCalc" = weighted calc of post and comment count * 10 weighted value
"@lCalc" = weighted calc of post and comment likes * 5 weighted value

プレクエリへの LEFT JOIN は、これらのクエリを 1 回実行し、すべてのレコードのサブクエリとしてヒットする代わりに、全体が結合されます。COALESCE() を使用すると、LEFT JOIN されたテーブルの結果にそのようなエントリがない場合、NULL 値でヒットして計算が台無しになることはないので、デフォルトで 000000 に設定しました。

ご質問の明確化

「AS AliasResult」として任意の QUERY を持つことができます。"As" を使用して、長いテーブル名を簡略化し、読みやすくすることもできます。エイリアスは、同じテーブルを別のエイリアスとして使用して、同様のコンテンツを別の目的で取得することもできます。

select
      MyAlias.SomeField
   from
      MySuperLongTableNameInDatabase MyAlias ...

select
      c.LastName,
      o.OrderAmount
   from
      customers c
         join orders o
            on c.customerID = o.customerID  ...

select
      PQ.SomeKey
   from
      ( select ST.SomeKey
           from SomeTable ST
           where ST.SomeDate between X and Y ) as PQ
         JOIN SomeOtherTable SOT
            on PQ.SomeKey = SOT.SomeKey ...

現在、上記の 3 番目のクエリは実用的ではありません (完全なクエリの結果、"PreQuery" を表すエイリアス "PQ" が生成されます)。これは、他の複雑な条件の特定のセットを事前に制限し、すべての最終結果のために他の多くのテーブルに追加の結合を行う前に、より小さなセットが必要な場合に実行できます。

「FROM」は実際のテーブルである必要はありませんが、それ自体がクエリである可能性があるため、クエリで使用される他の場所では、このプレクエリの結果セットを参照する方法を知っている必要があります。

また、フィールドをクエリする場合、それらも「As FinalColumnName」にすることで、結果がどこで使用されるかを単純化できます。

... から CourtesyName として CONCAT( User.Salutation, User.LastName ) を選択します。

... から OrderTotalWithTax として Order.NonTaxable + Order.Taxable + ( Order.Taxable * Order.SalesTaxRate ) を選択します。

"As" columnName は集約である必要はありませんが、最も一般的にはそのように見られます。

ここで、MySQL 変数に関して... ストアド プロシージャを実行している場合、多くの人は、プロシージャの残りの部分の前にデフォルト値を設定することを事前に宣言します。その結果に「エイリアス」参照を設定して与えるだけで、クエリでインラインで実行できます。これらの変数を実行すると、select は常に SINGLE RECORD 相当の値を返すことをシミュレートします。クエリ内で使用される更新可能な単一のレコードに似ています。クエリ内の残りのテーブルに影響を与えない可能性があるため、特定の「結合」条件を適用する必要はありません...本質的に、デカルト結果を作成しますが、他のテーブルに対する1つのレコードは作成されませんとにかく重複するので、下流にダメージはありません。

select 
       ...
   from 
      ( select @SomeVar := 0,
               @SomeDate := curdate(),
               @SomeString := "hello" ) as SQLVars

さて、sqlvars がどのように機能するか。線形プログラムを考えてみてください... クエリの実行とまったく同じ順序で 1 つのコマンドが実行されます。その値は、次回に備えて "SQLVars" レコードに再格納されます。ただし、SQLVars.SomeVar または SQLVars.SomeDate として参照するのではなく、単に @SomeVar := someNewValue として参照します。これで、@var がクエリで使用されると、結果セットにも "As ColumnName" として格納されます。場合によっては、これは次のレコードを準備するための単なるプレースホルダーの計算値になることがあります。各値は、次の行で直接使用できます。したがって、次のサンプルを考えると...

select
      @SomeVar := SomeVar * 2 as FirstVal,
      @SomeVar := SomeVar * 2 as SecondVal,
      @SomeVar := SomeVar * 2 as ThirdVal
   from
      ( select @SomeVar := 1 ) sqlvars,
      AnotherTable
   limit 3

Will result in 3 records with the values of 

FirstVal    SecondVal   ThirdVal
2           4           8
16          32          64
128         256         512

@SomeVar の値が各列で使用されるときにどのように使用されるかに注意してください...したがって、同じレコードであっても、更新された値は次の列ですぐに利用できます... / お客様ごとのランキング...

select
      o.CustomerID,
      o.OrderID
      @SeqNo := if( @LastID = o.CustomerID, @SeqNo +1, 1 ) as CustomerSequence,
      @LastID := o.CustomerID as PlaceHolderToSaveForNextRecordCompare
   from
      orders o,
      ( select @SeqNo := 0, @LastID := 0 ) sqlvars
   order by
      o.CustomerID

「Order By」句は、結果が最初に順番に返されるように強制します。したがって、ここでは、顧客ごとのレコードが返されます。初めて、LastID は 0 で、顧客 ID は ...5 です。異なるため、@SeqNo として 1 を返し、その顧客 ID を次のレコードの @LastID フィールドに保存します。さて、顧客の次のレコード... 最後の ID は同じなので、@SeqNo (現在 = 1) を取り、1 に 1 を追加して、同じ顧客の #2 になります... パスを続行します.. .

クエリの書き方を改善するために、MySQL タグを見て、いくつかの重要な貢献者を調べてください。質問といくつかの複雑な回答、および問題解決のしくみを調べます。始めたばかりで完全に有能で評判スコアが低い人が他にいないことは言うまでもありませんが、誰が良い答えを出し、その理由を見つけることができます. 投稿された回答の履歴も見てください。読んでフォローすればするほど、より複雑なクエリをより適切に記述できるようになります。

于 2012-05-04T13:22:39.837 に答える
1
  1. 各列にサブクエリを使用する代わりに、このクエリを Group By 句に変換できます。
  2. 関係パラメーターにインデックスを作成できます (クエリ応答を最適化する最も役立つ方法です)。
于 2012-05-04T12:23:02.763 に答える
1

@me (画像 1 を参照) と @DRapp (画像 2 を参照)の違いを参照してください。クエリの実行時間と説明。@Drappの回答を読んだとき、このクエリで何が間違っているのか、なぜクエリに時間がかかるのか、基本的に答えは非常に単純であることに気付きました。セッション変数、エイリアスと結合...

画像1 実行時間 ( 00:02:56:321)

ここに画像の説明を入力

画像2 実行時(00:00:32:860

ここに画像の説明を入力

于 2012-05-06T20:00:37.433 に答える
1

1000 のユーザー レコードは、まったく多くのデータではありません。

データベース自体でできる作業があるかもしれません:

1) 関連するインデックスを外部キーに設定しましたか (各テーブルの user_id に設定されたインデックス)? クエリの前に EXPLAIN を実行してみてくださいhttp://www.slideshare.net/phpcodemonkey/mysql-explain-explained

2) データ型は正しいですか?

于 2012-05-04T12:24:57.383 に答える