3

ユーザー ランキングを中心とした Web サイトを運営していますが、ユーザー数が 50,000 人を超えており、5 分ごとにランクを更新するためにすべてのユーザーをループ処理するためにサーバーに負荷がかかっています。少なくとも 5 分ごとにランクを簡単に更新するために使用できるより良い方法はありますか? それはphpである必要はありません.perlスクリプトのように実行されるものか、そのようなものがより良い仕事をすることができるのであれば何かかもしれません(なぜそうなるのかはわかりませんが、私のオプションはここで開きます)。

これは、ランクを更新するために現在行っていることです。

$get_users = mysql_query("SELECT id FROM users WHERE status = '1' ORDER BY month_score DESC");
$i=0;
while ($a = mysql_fetch_array($get_users)) {
    $i++;
    mysql_query("UPDATE users SET month_rank = '$i' WHERE id = '$a[id]'");
}

更新(解決策):

これは、50,000 行すべてを実行して更新するのに 1/2 秒未満しかかからないソリューション コードです (Tom Haigh が提案するように主キーをランク付けします)。

mysql_query("TRUNCATE TABLE userRanks");
mysql_query("INSERT INTO userRanks (userid) SELECT id FROM users WHERE status = '1' ORDER BY month_score DESC");
mysql_query("UPDATE users, userRanks SET users.month_rank = userRanks.rank WHERE users.id = userRanks.id");
4

8 に答える 8

8

自動インクリメントuserRanks.rankの主キーを作成します。userRanks次に、ユーザーIDをランクの降順で挿入する とrank、すべての行の列がインクリメントされます。これは非常に高速である必要があります。

TRUNCATE TABLE userRanks;
INSERT INTO userRanks (userid) SELECT id FROM users WHERE status = '1' ORDER BY month_score DESC;
UPDATE users, userRanks SET users.month_rank = userRanks.rank WHERE users.id = userRanks.id;
于 2009-06-04T10:58:46.463 に答える
3

私の最初の質問は、なぜこのポーリングタイプの操作を5分ごとに実行するのかということです。

確かに、ランクの変更は何らかのイベントに応答して行われ、そのイベントが発生したときにデータベース内のいくつかの行に変更をローカライズできます。50,000人のユーザーベース全体が5分ごとにランキングを変更することはないと確信しています。

はユーザーのランクが変わったことを示していると思い"status = '1'"ますので、ユーザーがランク変更をトリガーしたときに設定するのではなく、その時点でランクを計算してみませんか?

再ランク付けのコストはすべての操作で償却されるため、これはより良い解決策のように思われます。

今、私はあなたがランク付けすることの意味を誤解しているかもしれません。その場合、私をまっすぐにしてください。

于 2009-06-04T06:25:27.790 に答える
3

一括更新の簡単な代替手段は、次のようなものです。

set @rnk = 0;
update users 
set month_rank = (@rnk := @rnk + 1)
order by month_score DESC

このコードは、更新ごとにインクリメントされるローカル変数 (@rnk) を使用します。行の順序付けられたリストに対して更新が行われるため、month_rank 列は行ごとに増分された値に設定されます。

于 2009-06-04T06:34:55.387 に答える
1

おそらく、時間または他のカテゴリごとにシャードを使用できます。しかし、前にこれを注意深く読んでください...

于 2009-06-04T10:57:57.913 に答える
1

ユーザーテーブルを行ごとに更新することは、時間のかかる作業になります。行ごとの更新が不要になるようにクエリを再編成できれば、より良いでしょう。

構文については100%確信がありませんが(これまでMySQLを使用したことがないため)、MS SQLServer2000で使用される構文のサンプルを次に示します。

DECLARE @tmp TABLE
(
    [MonthRank] [INT] NOT NULL,
    [UserId] [INT] NOT NULL,
)

INSERT INTO @tmp ([UserId])
SELECT [id] 
FROM [users] 
WHERE [status] = '1' 
ORDER BY [month_score] DESC

UPDATE users 
SET month_rank = [tmp].[MonthRank]
FROM @tmp AS [tmp], [users]
WHERE [users].[Id] = [tmp].[UserId]

MS SQL Server 2005/2008では、おそらくCTEを使用します。

于 2009-06-04T06:23:55.967 に答える
1

問題はさまざまな方法で処理できます。正直なところ、サーバーからのより詳細な情報は、まったく異なる方向性を示している可能性があります. しかし、そのようにすると、読み取り頻度の高いテーブルで 50,000 の小さなロックが発生します。ステージング テーブルを使用してから何らかの遷移を行うと、パフォーマンスが向上する可能性があります。誰も読んでいないテーブルへの挿入は、おそらくより良いものになるでしょう。

検討

mysql_query("delete from month_rank_staging;");
while(bla){
  mysql_query("insert into month_rank_staging values ('$id', '$i');");
}
mysql_query("update month_rank_staging src, users set users.month_rank=src.month_rank where src.id=users.id;");

これにより、テーブルに 1 つの (より大きな) ロックが発生しますが、状況が改善される可能性があります。ただし、パフォーマンスの問題の真の原因によっては、それがベースから外れている可能性があります。おそらく、ログ、mysql 構成、データベース接続などを詳しく調べる必要があります。

于 2009-06-04T06:37:09.720 に答える
1

ランク処理と更新実行を分割できます。したがって、すべてのデータを調べて、クエリを処理します。各更新ステートメントをキャッシュに追加します。処理が完了したら、更新を実行します。他の投稿で述べたように、UPDATE の WHERE 部分で auto_increment に設定された主キーを参照する必要があります。これにより、更新が処理のパフォーマンスに干渉するのを防ぐことができます。また、処理キューの後のユーザーが、前に処理されたユーザーの値を不正に利用することも防止します (あるユーザーのランクが別のユーザーのランクに影響する場合)。また、処理コードが行う SELECTS からデータベースがテーブル キャッシュをクリアするのを防ぎます。

于 2009-06-05T03:24:18.327 に答える
1

内部でクエリを実行するかなりのサイズのループがある場合は常に、アンチパターンが発生する可能性が非常に高くなります。より多くの情報でスキーマと処理要件を調べ、ループなしでジョブ全体を実行できるかどうかを確認できます。

ランキングの割り当てと比較して、スコアの計算にどのくらいの時間がかかりますか?

于 2009-06-04T06:28:20.327 に答える