3

グラフのデータを保存するための、等しく実行可能な 3 つの異なる方法を思いつきました。

問題のグラフは「時間の経過に伴うさまざまなカテゴリでのプレーヤーのスコア」です。カテゴリには、「建物」、「アイテム」、「クエストの完了」、「実績」などがあります。

方法 1:

CREATE TABLE `graphdata` (
    `userid` INT UNSIGNED NOT NULL,
    `date` DATE NOT NULL,
    `category` ENUM('buildings','items',...) NOT NULL,
    `score` FLOAT UNSIGNED NOT NULL,
    PRIMARY KEY (`userid`, `date`, `category`),
    INDEX `userid` (`userid`),
    INDEX `date` (`date`)
) ENGINE=InnoDB

このテーブルには、ユーザー/日付/カテゴリの組み合わせごとに 1 行が含まれます。ユーザーのデータを表示するには、 by を選択しuseridます。古いエントリは次の方法で消去されます。

DELETE FROM `graphdata` WHERE `date` < DATE_ADD(NOW(),INTERVAL -1 WEEK)

方法 2:

CREATE TABLE `graphdata` (
    `userid` INT UNSIGNED NOT NULL,
    `buildings-1day` FLOAT UNSIGNED NOT NULL,
    `buildings-2day` FLOAT UNSIGNED NOT NULL,
    ... (and so on for each category up to `-7day`
    PRIMARY KEY (`userid`)
)

ユーザー ID による選択は、主キーであるため高速です。次のように、毎日のスコアがフィールドの下にシフトされます。

... SET `buildings-3day`=`buildings-2day`, `buildings-2day`=`buildings-1day`...

エントリは削除されません (ユーザーがアカウントを削除しない限り)。行はクエリで追加/更新できINSERT...ON DUPLICATE KEY UPDATEます。

方法 3:

JSON でエンコードされたスコア データの配列を含む、ユーザーごとに 1 つのファイルを使用します。いずれにせよデータは AJAX JSON 呼び出しによって取得されるため、サーバーにストレスを与えることなく、ファイルを静的に取得することができます (次の深夜までキャッシュすることもできます)。サーバーは毎日各ファイルを実行し、shift()各アレイの最も古いスコアからpush()最後に新しいスコアへと進みます。


個人的には方法 3 が群を抜いて優れていると思いますが、データベースの代わりにファイルを使用することについて悪いことを聞いたことがあります。

2 つのデータベース ソリューションのうち、以前のプロジェクトの 1 つに方法 2 を実装しましたが、これは非常にうまく機能しているようです。方法 1 は、リレーショナル データベースなどをより有効に利用できるという点で「優れている」ように見えますが、(number of users) * (number of categories) * 7行が含まれるため、大きな数になる可能性があるという点で少し心配です。

どの方法を使用するかについて最終的な決定を下すのに役立つ可能性がある、私が見逃しているものはありますか? 1、2、3、または上記のどれでもない?

4

2 に答える 2

3

リレーショナル データベースを使用する場合は、方法 1 が方法 2 よりもはるかに優れています。正規化されているため、保守と検索が簡単です。dateフィールドを a に変更してtimestamp呼び出しますadded_on(または、「日付」のような予約語ではない何か)。score_idそして、user_id/date/category が一意である必要がないように、auto_increment 主キーを追加します。そうすれば、ユーザーが同じ秒内に建物のスコアを 2 回インクリメントできたとしても、両方とも記録されます。

2 番目の方法では、すべてのレコードを毎日更新する必要があります。最初の方法は挿入のみを行い、更新は行わないため、各レコードは 1 回だけ書き込まれます。

... セットbuildings-3day= buildings-2day, buildings-2day= buildings-1day...

テーブル内のすべてのレコードを時間の終わりまで毎日更新したいですか? !

主キーであるため、ユーザーIDによる選択が高速です

user_idは方法 1 の主キーの最初のフィールドであるため、ルックアップも同様に高速になります。通常のインデックスの最初のフィールドとして (これは私が上で提案したことです)、それでも非常に高速です。

リレーショナル データベースの考え方は、各行が単一のインスタンス/アクション/オカレンスを表すということです。そのため、ユーザーが自分のスコアに影響を与えるために何かを行った場合、ユーザーが行ったことを記録する INSERT を実行します。このようなデータからいつでもサマリーを作成できます。しかし、この種のデータを要約から取得することはできません。

2 つ目は、古いデータを削除することを無意識に心配しているようです。なんで?選択クエリには、古いデータを自動的に除外する日付範囲があります。また、パフォーマンスが気になる場合は 、行の経過時間に基づいてテーブルを分割するか、古いレコードを定期的に削除するように cron ジョブを設定できます。

ETA: ファイルに保存された JSON について

これは、方法 2 の欠点 (検索が難しく、すべてのファイルを毎日更新する必要がある) と、ファイル アクセスの追加の欠点を組み合わせているように思えます。ファイル アクセスは高価です。ファイル書き込みはなおさらです。本当に集計データを保存したい場合は、データが要求されたときにのみクエリを実行し、その結果を user_id ごとに集計テーブルに保存します。テーブルは JSON 文字列を保持できます。

CREATE TABLE score_summaries(
user_id INT unsigned NOT NULL PRIMARY KEY,
gen_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
json_data TEXT NOT NULL DEFAULT '{}'
);

例えば:

Bob (user_id=7) が初めてゲームにログインします。彼は、毎週の統計を表示するプロフィール ページにいます。これらのクエリが実行されました:

SELECT json_data FROM score_summaries 
  WHERE user_id=7 
    AND gen_date > DATE_SUB(CURDATE() INTERVAL 1 DAY); 
//returns nothing so generate summary record

SELECT DATE(added_on), category, SUM(score) 
  FROM scores WHERE user_id=7 AND added_on < CURDATE() AND > DATE_SUB(CURDATE(), INTERVAL 1 WEEK)
  GROUP BY DATE(added_on), category; //never include today's data, encode as json with php

INSERT INTO score_summaries(user_id, json_data)
  VALUES(7, '$json') //from PHP, in this case $json == NULL
  ON DUPLICATE KEY UPDATE json_data=VALUES(json_data)

//use $json for presentation too

今日のスコアは必要に応じて生成され、サマリーには保存されません。ボブが今日再びスコアを表示した場合、過去のスコアはサマリー テーブルから取得されるか、最初のリクエストの後にセッションに保存される可能性があります。Bob が 1 週間訪問しない場合、要約を生成する必要はありません。

于 2012-05-28T20:12:38.113 に答える
1

方法 1 は、明らかに勝者のように思えます。単一のテーブル (graphData) のサイズが大きすぎることが懸念される場合は、作成することでサイズを減らすことができます

CREATE TABLE `graphdata` (
    `graphDataId` INT UNSIGNED NOT NULL,
    `categoryId` INT NOT NULL,
    `score` FLOAT UNSIGNED NOT NULL,
    PRIMARY KEY (`GraphDataId'),
) ENGINE=InnoDB

2 つのテーブルを作成するよりも、graphDataId と userId を接続する情報が明らかに必要なためです。

create table 'graphDataUser'(
         `graphDataId` INT UNSIGNED NOT NULL,
        `userId` INT NOT NULL,
)ENGINE=InnoDB

およびgraphDataId日付接続

create table 'graphDataDate'(
         `graphDataId` INT UNSIGNED NOT NULL,
        'graphDataDate' DATE NOT NULL
)ENGINE=InnoDB

ほとんどの dba は行数に関して良い仕事をしているので、テーブルに含まれる行数について本当に心配する必要はないと思います。あなたの仕事は、データを取得するタスクが何であれ、簡単に取得できるようにフォーマットされたデータを取得することだけです。そのアドバイスを使用すると、長期的には報われるはずです。

于 2012-05-28T20:15:15.777 に答える