1

私の会社がリリースした Facebook レーシング ゲームのグローバル リーダーボードを実装する方法に取り組んでいます。私がやりたいことは、プレイヤーのユーザー ID とレースの時間を保存できるようにすることです。以下のような表があります。

+--------+-----------------------+------+-----+---------+-------+
| Field  | Type                  | Null | Key | Default | Extra |
+--------+-----------------------+------+-----+---------+-------+
| userID | mediumint(8) unsigned | NO   | PRI | 0       |       |
| time   | time                  | YES  | MUL | NULL    |       |
+--------+-----------------------+------+-----+---------+-------+

そして、次のようなデータのサンプルセット:

+--------+----------+
| userID | time     |
+--------+----------+
| 505610 | 10:10:10 |
| 544222 | 10:10:10 |
| 547278 | 10:10:10 |
| 659241 | 10:10:10 |
| 681087 | 10:10:10 |
+--------+----------+

私のクエリは PHP から来ます。無制限のリソースがあると仮定すると、次のことができます。

$q1 = "Set @rank := 0";
$q2 = "select @rank:=@rank+1 as rank,userID,time from highscore order by time asc where userID=$someUserID";
$q3 = "Set @rank := 0";
$q4 = "select @rank:=@rank+1 as rank,userID,time from highscore order by time asc where rank > $rankFromSecondQuery - 10 and rank < $rankFromSecondQuery + 10";

しかし、無限のリソースはありません。Facebook のソーシャル ゲームに参加するため、これを拡張して何百万人ものプレイヤーをサポートできるようにする必要があります。そのため、Google 中をクロールするのに数日を費やした後、クエリを次のようにまとめることができました。

$q5 = "select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=$someUserID"
$q6 = "select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where rank > $rankFromFirstQuery - 10 and rank < $rankFromSecondQuery + 10";

これは機能しますが、クエリあたりの平均実行時間は約 2.3 秒で、あまりきれいではありません。

編集: $q5 と $q6 を実行すると、次のようになります。

mysql> select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345;                                                                          
+--------+--------+----------+
| rank   | userID | time     |
+--------+--------+----------+
| 423105 |  11345 | 12:47:23 |
+--------+--------+----------+
1 row in set (2.42 sec)

mysql> select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where rank>423100 and rank<423110;
+--------+---------+----------+
| rank   | userID  | time     |
+--------+---------+----------+
| 423101 | 2416665 | 12:47:22 |
| 423102 | 2419720 | 12:47:22 |
| 423103 | 2426606 | 12:47:22 |
| 423104 | 2488517 | 12:47:22 |
| 423105 |   11345 | 12:47:23 |
| 423106 |   92350 | 12:47:23 |
| 423107 |   94277 | 12:47:23 |
| 423108 |  114685 | 12:47:23 |
| 423109 |  135434 | 12:47:23 |
+--------+---------+----------+
9 rows in set (2.58 sec)

以下は、Explain 拡張ブロック $q5 で、$q6 のブロックはほぼ同じに見えます。

mysql> explain select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345;
+----+-------------+------------+--------+---------------+----------+---------+------+---------+----------------+
| id | select_type | table      | type   | possible_keys | key      | key_len | ref  | rows    | Extra          |
+----+-------------+------------+--------+---------------+----------+---------+------+---------+----------------+
|  1 | PRIMARY     | <derived2> | system | NULL          | NULL     | NULL    | NULL |       1 |                |
|  1 | PRIMARY     | <derived3> | ALL    | NULL          | NULL     | NULL    | NULL | 2500000 | Using where    |
|  3 | DERIVED     | highscore  | index  | NULL          | idx_time | 4       | NULL | 2500842 | Using index    |
|  2 | DERIVED     | NULL       | NULL   | NULL          | NULL     | NULL    | NULL |    NULL | No tables used |
+----+-------------+------------+--------+---------------+----------+---------+------+---------+----------------+

したがって、最終的に私が本当にやりたいことは、これを 1 つのクエリにまとめて、CPU の高いサーバーを 1 つまたは 2 つ使用して実行時間を短縮できるようにすることです。それか、テーブル内のすべての行にヒットしている Explain ブロックの serve3 行に関連付けられているクエリの一部のインデックスにヒットする方法を見つけたいと思います。

これまでに成功せずに試したいくつかのクエリを次に示します。

select rank,userID,time from (select @rank:=0) r, (select @playerRank := rank from (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345) as myFoo where @playerRank>423100 and @playerRank<423110;
select rank,userID,time from (select @playerRank := rank from (select @rank := 0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345) as myFoo where @playerRank>423100 and @playerRank<423110;
select * from (select @rank:=0) r, (select @playerRank := userID from (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345) as myFoo where @playerRank>423100 and @playerRank<423110;

最初の 2 つのゲームでは、「ERROR 1054 (42S22): Unknown colum 'rank' in 'field list'」エラーが発生し、3 番目のゲームでは、探していたデータではなく空のセットが返されました。

上記の 2 つのクエリをインデックスにヒットさせて実行時間を短縮する方法、または 2 つのクエリを 1 つに結合する方法のいずれかを知っている人はいますか? また、MySQL 構成設定の微調整や Percona などの使用経験があり、その経験を共有したい場合は、Percona のようなものを使用するなどのチューニング/最適化にもオープンです。

4

3 に答える 3

0

count() を使用して最初にランクを取得できます。これにより、最初のクエリのパフォーマンスが少し向上するはずです。

SELECT COUNT(h.userID) as rank, h2.userID, h2.time
   FROM highscore h
   LEFT OUTER JOIN highscore h2 ON (h.time <= h2.time)
   WHERE h2.userID = ?

次に、近くのランキングを照会するために Puggan の手法を使用できます。

SELECT ... ORDER BY time LIMIT $lowest_rank, 21
于 2012-07-20T00:57:45.523 に答える
0

あなたが達成しようとしていることを達成するために、この代替ソリューションを提案したいと思います。

ランクを格納する別のテーブルを作成します。ユーザーが自分のランクを知りたがるたびに計算したり、既存のテーブルに含めたりしないでください。ランクを別のテーブルに配置すると、スコアの更新がランクの計算と競合する場合のロック競合の問題が緩和されることが期待されます。

定期的にランクを再計算します。この再計算を行う場合は、ランク テーブルを切り捨て、最初から再作成します。これを一括ロード操作 (LOAD DATA INFILE) で行うか、MyISAM テーブルにします (テーブルの最後に挿入すると高速です)。どちらの方法でも、実際にテーブルを書き出すのは比較的高速です。少なくとも、既に配置されているテーブルの何百万もの行を更新するよりも高速です。これらの方法はどちらもランク テーブルを脆くし、クラッシュが発生した場合の損失に対して脆弱になりますが、これは本質的に一時的なデータであるため問題ありません。スコア テーブルが安定している限り、安全です。一定間隔で再計算することで、壁にぶつかるまでプレイ回数が増えるにつれて計算を頻繁に行わなければならないという問題を回避できます。

ユーザーのスコアが上位 100 以内の場合は、すぐに新しいスコアを押し出します。ユーザーは、トップ 100 をブラウズして、誰が最高のスコアを持っているかを確認したいと思うかもしれません。そのポイントより下のリストを実際に閲覧したい人はほとんどいないと思います。

ユーザーが友達のスコアをすぐに確認できるようにし、相互に比較した相対的なランクも表示します。これはおそらく、ほとんどのユーザーが関心を持っているランキングです。私の妻が Facebook のゲームをプレイしているとき、全体的なランキングには関心がないことは知っていますが、彼女は大学の同級生に勝ったかどうかを知りたがっています。

ユーザーの最新のプレイ後に無効化されたプレイヤーとそのフレンドの総合ランクを表示し、次の更新の準備が整うたびにそれらを非同期にロードします。

もう1つの考慮事項は、このゲームが数年間続くと、スコアボードが、特にローエンド周辺で、非アクティブなプレーヤーの古いスコアで詰まるということです. これらのスコアをアーカイブする価値があるかどうかを検討することをお勧めします。たとえば、スコアボードの下位 75% のプレーヤーは、過去 6 か月以内にプレーした場合にのみランキングで考慮されると言えます。次に、それらのスコアをアーカイブ テーブルに移動します。ここで記憶され、そのプレーヤーが戻ってきた場合にスコアボードに復元できますが、ランキングを計算するたびに並べ替えに含める必要はありません。はい、これは間違いなくあなたのランキングを「真実」ではなくしますが、とにかく人々はただ楽しみのために遊んでいます. ランキングの見栄えを良くするという副作用もあり、これも楽しいです。

于 2012-07-20T16:13:57.247 に答える
0

runnint$q5の後、ユーザーのランクを知る必要があります。その後、制限を使用して適切な行を取得できるはずです

$lowest_rank_to_fetch = max(0, $rankFromFirstQuery - 10);
$q6l = "SELECT userID, time
        FROM highscore
        ORDER BY time ASC
        LIMIT {$lowest_rank_to_fetch}, 21";

/* some execute query function */

foreach(range($lowest_rank_to_fetch, $lowest_rank_to_fetch+21) as $current_rank)
{
   /* some database fetch function */
   /* add $current_rank to result */
}
于 2012-07-19T22:22:57.913 に答える