12

説明:

  • 各ユーザーの合計クレジット (評判) を表示するスクリプトがあり、データベースにすべてのユーザーの獲得クレジットの履歴テーブルがあります。

これが私の履歴データベーステーブルのサンプルです:

 +----------------------------------------------+
 | DATE     ID     USERNAME       CREDITS       |
 +----------------------------------------------+
 | ...      1         X              12         |
 | ...      2         E               2         |
 | ...      3         X               1         |
 | ...      4         X              -7         |
 | ...      5         O               4         |
 +----------------------------------------------+
  • 私のスクリプトは SELECT SUM FROM table WHERE username='X' を使用し、それをエコーするため、この場合、ユーザー X (12 + 1 - 7) の場合は 6 をエコーし​​ます

質問:

  1. 私が知りたかったのは、履歴テーブルが非常に巨大な場合、これ (すべての履歴の SELECT SUM を使用してユーザーのクレジットを表示するINSTEAD代わりに、ユーザーの合計クレジット用に別のテーブルを作成すること) が問題になるのではないかということです。(数年後に +100,000,000 レコードとしましょう)

  2. これは、ほとんどのプロのプログラマーが行うことですか? (そうでない場合、それは何ですか)

  3. 履歴セクションについてはどうですか。ユーザーがクレジット履歴を確認したい場合は、* SELECT * するか、しない場合にLIMIT 100レコードのように制限する必要があります (パフォーマンスのため)。

  4. これは、ページが更新されるたびに、またはページが変更されるたびに実行されることになっていますか? (1000 人のユーザーがオンラインで、この SELECT クエリが更新のたびに適用される場合、サーバーの速度は低下しません)

EDIT回答後:

しかし、合計を別のテーブルに保持して自動的に更新する必要がある場合、2 つの問題があります。

  1. ユーザーがいくつかのクレジットを受け取ったときに正確にそれを行うと、ユーザーが2つの異なるクレジットをまったく同時に受け取った可能性はありません(可能です)。レコードが 1 つある場合) クレジットが 1 つなくなる可能性があります。または、この問題の解決策がある場合、私はそれを認識していません。

  2. Cron ジョブを頻繁に実行するように設定した場合、cron ジョブが合計テーブルを更新するまで、ユーザー クレジットは最新ではありません。

4

7 に答える 7

7

ユーザーがいくつかのクレジットを受け取ったときに正確に行うと、ユーザーが2つの異なるクレジットをまったく同時に受け取った可能性があり(非常に可能性があります)、自動インクリメントを合計テーブルに入れることができないため(各ユーザーは1つのレコードしか持たないため) ) 1 クレジットを逃して合計テーブルに追加しない可能性があります。または、この問題の解決策がある場合、私はそれを認識していません。これらの状況で AI を使用する必要があるのは今だけです。

私たちはそれを見逃すことはありません。次の SQL ステートメントを確認します。

INSERT INTO history SET username = 'X', credits = 2;
UPDATE users SET credits_sum = (SELECT SUM(credits) FROM `history` WHERE username = 'X') WHERE username = 'X';

クレジットを追加する 2 つのイベントが発生した場合でも、(アプリケーションではなく) データベースに格納されたデータから更新されるため、credits_sum は最新の状態になります。そのシナリオでは、いくつかの違いがある場合があります)。

もちろん、のusers代わりにテーブルの主キーを使用する必要がありusername = 'X'ます。

于 2013-06-30T23:36:28.940 に答える
3
  1. はい、そうなります。(小) 合計を別のテーブルに保持し、ストアド プロシージャでそれらを自動的に更新することをお勧めします。
  2. 大規模な場合、非正規化を開始する必要があるため、合計を維持して、常に再計算する必要がないようにします。
  3. ページネーションは、パフォーマンスと使いやすさの両方にとって良い考えです。何千行も表示しても読みやすくはなりません。ただし、範囲でフィルタリングすることをお勧めします(つまりid BETWEEN x AND yLIMIT 100 OFFSET 500
  4. はい、そうなります。頻繁に変更されないものがある場合。キャッシュします。たとえば... RedisまたはMemcachedで。
于 2013-06-30T17:16:33.037 に答える
3

各ユーザーの総クレジットを追跡する別のテーブルを提案し、トリガーを使用してそのテーブルを最新の状態に保ちます。

合計クレジットを追跡するテーブルが次のようになると仮定します。

CREATE TABLE reputation (
  username varchar(20) primary key,
  total int
)

トリガーは次のようになります。

CREATE TRIGGER historyInsert AFTER INSERT ON history
FOR EACH ROW BEGIN
  INSERT INTO reputation (username,total)
  VALUES (NEW.username,NEW.credits)
  ON DUPLICATE KEY UPDATE total = total + NEW.credits;
END

履歴テーブルに何かが挿入されると、このトリガーが起動されます。挿入された行ごとに、トリガーは評価テーブルに新しい値を挿入するか、ユーザーが既に存在する場合は合計値を更新します。

これINSERT ... ON DUPLICATE KEY UPDATEは MySQL のアトミック操作であるため、2 つの更新が同時に発生することを心配する必要はありません。

SQL フィドルのデモ

別のレピュテーションテーブルを作成する代わりに、何らかの形式のユーザーテーブルが既にある場合は、そこに各ユーザーの合計クレジットを常に格納できます。各ユーザーのエントリが既にあると仮定すると、トリガーは新しいエントリの作成を心配する必要がなく、エントリを更新するだけです。

トリガー コードはさらに単純になります。

CREATE TRIGGER historyInsert AFTER INSERT ON history
FOR EACH ROW BEGIN
  UPDATE users SET total = total + NEW.credits
  WHERE username = NEW.username
END

繰り返しますが、このUPDATEクエリはアトミックです。合計フィールドをインクリメントするだけなので、2 つの更新が同時に発生した場合、それらは互いに上書きされず、両方の金額が合計に追加されます。

これは、新しい値が挿入されるたびに履歴全体の SUM を計算するよりもはるかに効率的です。

于 2013-07-04T00:35:27.617 に答える
2
  1. ここにある他の人たちと同様に、ユーザー クレジットを「ライブ」テーブルと「履歴」テーブルに分割することをお勧めします。夜間 (または毎週など) のジョブで、レコードをライブから履歴に移行できます。「ライブ」テーブル (およびサポートしているインデックス) の大部分がメモリ内に収まるようにコンパクトに保つ​​ことができれば、パフォーマンスは問題になりません。履歴テーブルを維持するために使用するジョブの最後に、3 番目の「総クレジット」テーブルを追加することをお勧めします。これにより、クレジットの合計 (今日のものを除く) を 1 回のインデックス読み取りで確認できます。

  2. おそらく、一度追加されたクレジットは不変です。したがって、変更されない場合、プログラムにそれらを何度も何度も追加するように強制することにはほとんど意味がありません。過去のクレジットのトランザクションの詳細が必要ない場合は、月ごとに合計しておいてください。

  3. 制限はいくらかは役に立ちますが、設計上の欠陥を浮き彫りにします: 参照しないレコードを保存しないでください: それらはディスク領域、インデックス領域、およびメモリを引き続き使用します。実際に必要なものについては、かなり合理的 (かつ冷酷) でなければなりません。あなたのビジネス モデルを見てください。ユーザーが自分のクレジット履歴を確認できるようにしたいのはなぜですか? そして、彼らが見ることができるものを任意の制限で遮断すると、彼らを遠ざけますか? ビジネスとユーザーのことをよく知っているので、自分でポリシーを理解できる必要があります。しかし、テクノロジーを政策のしもべにするのであって、その逆ではありません。

  4. これらの質問はアーキテクチャ全体に当てはまります。クエリのコストが高い場合、Web セッション中にクエリ結果をキャッシュする方法は確かに存在します。これは、全体的なアーキテクチャと使用しているテクノロジ スタックによって異なります。

--- 2 番目の質問セット

  1. 日の境界でクレジットを履歴に移動します。「ライブ」テーブルでも、選択基準の一部として現在の日付を使用します。そうすれば、誤ってクレジットを落とす (または二重にカウントする) ことはありません。

  2. わかりません。クレジットは、獲得された正確な瞬間に「ライブ」テーブルに挿入され、1 日の境界で履歴テーブルにコピーされます。「ライブ」テーブルは常にその日の最新の状態であり、履歴テーブルは1 日より古いものに対して常に最新です。

あなたのプロジェクトがうまくいくことを願っています...

于 2013-07-05T23:44:02.150 に答える
1

私が言いたいのは、現在の履歴データを追跡するだけでなく、最終結果をクレジット テーブルまたはユーザー テーブルのプロパティにキャッシュすることです。

擬似コード:

 function postCreditTransaction($username, integer $credit){
      $db->insert("credit_history", array("USERNAME"=>$username, "CREDIT"=>$credit));
      $db->update("update user_table set credit = credit + $credit where username = ".$db->quote($username));
 }

これにより、クレジット履歴によって提供される詳細が提供されますが、合計への低脂肪アクセスが得られます.

すべてが順調に進んでいることを確認するために、キャッシュ フィールドにキャッシュされた値に対して credit_history テーブルの定期的な監査を実行できます。

于 2013-07-08T04:00:59.413 に答える
1

では、短い履歴書から始めましょう。

  1. はい、パフォーマンスのために、事前に計算されたレピュテーションを保存する必要があります。
  2. ユーザー情報を含むテーブルがある場合 - フィールド "reputation_sum" を追加します (このデータを分離する意味はありません)、そうでない場合 - 特別なテーブルを作成します。
  3. 評判が変わると違いがわかります。その違いを「reputation_sum」に追加します。

つまり、「reputation_sum」の新しい値を計算するために「SELECT SUM of all history...」を使用しないでください。「履歴」テーブルからレコードを追加/更新/削除すると、total_reputation_change_value が計算され、「履歴」テーブルのすべてのレコードの合計を再計算せずに「reputation_sum」が更新されます。INSERT 操作の total_reputation_change_value は次のようになります - 「クレジット」フィールドの値。DELETE についても同じですが、単項マイナスが付きます。UPDATE の古い値と新しい値の違い。レピュテーションが頻繁に変化する場合、これにより、1 秒あたりのリクエスト数が大幅に増加します。これはまた、データの整合性をもう少し侵害します。これが怖い場合は、履歴から定期的にレコードを合計して「reputation_sum」データを更新する特別な cron ジョブを作成してください。

また、USERNAME を外部キーとして使用しないことをお勧めします (「users」テーブルがあり、これが外部キーである場合)。整数の USERID を作成することをお勧めします。履歴テーブルの検索が高速になります。

それでは、質問にお答えしましょう。

履歴テーブルが非常に大きい場合、これ (すべての履歴の SELECT SUM を使用してユーザーのクレジットの合計を示す別のテーブルを使用する代わりに、ユーザーのクレジットを表示する) が問題になるのではないか知りたかったのですか? (数年後に +100,000,000 レコードとしましょう)

ええ、「数年後に+100,000,000レコードとしましょう」というテーブルから毎回評判を計算すると、計算量のために非常に非効率になります。十分な数のサーバーがあれば、遅延はないかもしれませんが、確実にそうなるでしょう)

これは、ほとんどのプロのプログラマーが行うことですか? (そうでない場合、それは何ですか)。

これは一般的な解決策であり、ほとんどの場合にうまく機能します。あなたにとって最適なものではないかもしれませんが、より良いアドバイスをするのに十分な情報がありません. この種の状況では、プロのプログラマーは、プロジェクトの詳細に応じて、一連のメソッドを使用できます。

このような問題の良い解決策は、データのキャッシュです。しかし、それは少し異なるニーズに役立ちます。ユーザーが複雑だが同一の要求を行い、データが頻繁に変更されないようにする必要があります。

データがあまり頻繁に変更されない場合は、他の最適化のトリックメイキング インデックスが有効です。

履歴セクションについてはどうですか。ユーザーがクレジット履歴を確認したい場合は、*SELECT*するかしないか (パフォーマンスのために) LIMIT 100 レコードのように制限する必要があります。

もちろんそうすべきです。ほとんどの場合、ユーザーは 100 個 (200、300 個) のアイテムすべてを同時に表示することはできません。また、毎回ではなく、すべてのレコードを検索します (私が理解しているように、このセクションには多くのレコードがあります)。ユーザーがすべてのレコードを表示したとしても、数秒または数分かかる場合があります。単一のリクエストに制限を使用すると、時間の経過とともに負荷が分散され、負荷のピークが減少します。これにより、ユーザーの平均パフォーマンスが向上します。

したがって、パフォーマンス上の利点を得るには、大量のコンテンツに対して部分読み込み機能を提供する必要があります。

これは、ページが更新されるたびに、またはページが変更されるたびに実行されることになっていますか? (1000 人のユーザーがオンラインで、この SELECT クエリが更新のたびに適用される場合、サーバーの速度は低下しません)

ユーザーのあらゆるアクティビティがサーバーの速度を低下させます。これは修正不可能なものです:) しかし、ここでは、必要な機能を取得するためにさまざまな方法を使用する効率について説明します。私に関しては、「1000 人のユーザーがオンラインで、この SELECT クエリが更新のたびに適用される場合」が何を意味するのかわかりません。これは、評判の良いユーザー レコードをたくさん見ることができるフォーラムですか? それとも、評判が 1 つしかないプロフィール ページですか? あるいは、オフラインではなく、その 1000 人のオンライン ユーザーの評判を見たいと思うでしょうか?

ユーザーがいくつかのクレジットを受け取ったときに正確に行う場合、ユーザーが2つの異なるクレジットをまったく同時に受け取った可能性はありません(可能です)。また、自動増分を合計テーブルに入れることができないため(各ユーザーのみレコードが 1 つある場合) クレジットが 1 つなくなる可能性があります。または、この問題の解決策がある場合、私はそれを認識していません。

トランザクションの整合性は DBMS の問題であるため、気にする必要はありません。レピュテーションが変更されるたびに、「reputation_sum」フィールドのみに変更を加える必要があります。つまり、SQLリクエストを実行するだけです。

Cron ジョブを頻繁に実行するように設定した場合、cron ジョブが合計テーブルを更新するまで、ユーザー クレジットは最新ではありません。

クローンを使用しないでください。または、必要に応じて、データの実現だけに使用します。

于 2013-07-08T10:48:12.073 に答える